diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..4f3270c --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/devcontainers/python:3.12 + +# Install uv +RUN pip install --no-cache-dir uv + +# Install additional tools +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends \ + make \ + curl \ + && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +# Set up workspace +WORKDIR /workspace + +# Create a non-root user (vscode user is already created by base image) +# Ensure proper permissions +RUN chown -R vscode:vscode /workspace + +USER vscode diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 0000000..1d6490e --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,63 @@ +# Dev Container Configuration + +This directory contains the configuration for developing in a containerized environment using Visual Studio Code's Dev Containers feature. + +## What's Included + +### Pre-installed Tools +- Python 3.12 +- uv package manager +- make +- git +- curl +- zsh with Oh My Zsh + +### VS Code Extensions +The following extensions are automatically installed: +- Python (Microsoft) +- Pylance (Microsoft) +- Ruff (Linter and Formatter) +- Mypy Type Checker +- Even Better TOML +- GitHub Copilot + +### VS Code Settings +Pre-configured settings for: +- Python interpreter pointing to workspace `.venv` +- Automatic formatting with Ruff on save +- pytest test discovery +- Linting and type checking integration + +### Port Forwarding +- Port 8000 is automatically forwarded for the FastAPI server + +### Automatic Setup +When the container is created, `uv sync` runs automatically to install all dependencies. + +## Usage + +1. Open this repository in VS Code +2. Install the "Dev Containers" extension if you haven't already +3. Press `F1` and select "Dev Containers: Reopen in Container" +4. Wait for the container to build (first time only) +5. Start coding! + +## Customization + +Edit the following files to customize your dev container: + +- `devcontainer.json` - Main configuration (extensions, settings, ports) +- `Dockerfile` - Add system packages or tools +- `docker-compose.yml` - Modify Docker Compose configuration + +## Benefits + +- **Consistent Environment**: Everyone on your team uses the same development environment +- **No Local Setup**: No need to install Python, uv, or other tools locally +- **Isolated**: Each project has its own containerized environment +- **Cross-Platform**: Works identically on macOS, Linux, and Windows + +## Learn More + +- [VS Code Dev Containers Documentation](https://code.visualstudio.com/docs/devcontainers/containers) +- [Dev Containers Tutorial](https://code.visualstudio.com/docs/devcontainers/tutorial) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..bd551c3 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,70 @@ +{ + "name": "OpsCtl Development", + "dockerComposeFile": "docker-compose.yml", + "service": "devcontainer", + "workspaceFolder": "/workspace", + + // Configure tool-specific properties + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance", + "charliermarsh.ruff", + "ms-python.mypy-type-checker", + "tamasfe.even-better-toml", + "github.copilot" + ], + "settings": { + "python.defaultInterpreterPath": "/workspace/.venv/bin/python", + "python.terminal.activateEnvironment": true, + "python.testing.pytestEnabled": true, + "python.testing.pytestArgs": ["-v"], + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.fixAll": "explicit" + } + }, + "mypy-type-checker.args": [ + "--config-file=pyproject.toml" + ], + "ruff.lint.args": [ + "--config=pyproject.toml" + ] + } + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally + "forwardPorts": [8000], + "portsAttributes": { + "8000": { + "label": "FastAPI", + "onAutoForward": "notify" + } + }, + + // Use 'postCreateCommand' to run commands after the container is created + "postCreateCommand": "uv sync", + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root + "remoteUser": "vscode", + + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": true, + "installOhMyZsh": true, + "upgradePackages": true, + "username": "vscode", + "userUid": "1000", + "userGid": "1000" + }, + "ghcr.io/devcontainers/features/git:1": { + "version": "latest", + "ppa": true + } + } +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..3a73384 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,11 @@ +services: + devcontainer: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + volumes: + - ..:/workspace:cached + command: sleep infinity + network_mode: host + environment: + - PYTHONUNBUFFERED=1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..aaafd4c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + run: pip install uv + + - name: Install dependencies + run: uv sync + + - name: Run linting + run: uv run ruff check . + + - name: Run type checking + run: uv run mypy packages/cli/src packages/api/src + + - name: Run tests + run: uv run pytest -v --cov=packages --cov-report=xml --cov-report=term + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.xml + fail_ci_if_error: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92b3d81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +env/ +ENV/ +.venv + +# uv +.uv/ +uv.lock + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Mypy +.mypy_cache/ +.dmypy.json +dmypy.json diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..05083d0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing to OpsCtl + +Thank you for considering contributing to this project! + +## How to Contribute + +1. **Fork the repository** +2. **Create a new branch** for your feature or bugfix +3. **Make your changes** following our coding standards +4. **Add tests** for your changes +5. **Run tests and linting** to ensure everything passes +6. **Submit a pull request** + +## Development Setup + +See [DEVELOPMENT.md](DEVELOPMENT.md) for detailed setup instructions. + +## Code Style + +This project uses: +- **ruff** for linting and formatting +- **mypy** for type checking +- **pytest** for testing + +Before submitting a PR, ensure: + +```bash +# All tests pass +uv run pytest + +# Code is formatted +uv run ruff format . + +# No linting errors +uv run ruff check . + +# No type errors +uv run mypy packages/cli/src packages/api/src +``` + +## Pull Request Guidelines + +- Write clear, descriptive commit messages +- Keep PRs focused on a single feature or bugfix +- Update documentation if needed +- Add tests for new functionality +- Ensure all CI checks pass + +## Reporting Issues + +When reporting issues, please include: +- A clear description of the problem +- Steps to reproduce +- Expected vs actual behavior +- Your environment (OS, Python version, etc.) + +## Questions? + +Feel free to open an issue for questions or discussions! diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..7d67d2a --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,338 @@ +# Development Guide + +## Getting Started + +This guide will help you get started with developing in this monorepo template. + +## Setup Options + +### Option 1: Dev Container (Recommended) + +The easiest way to get started with a consistent development environment across all platforms. + +**Prerequisites:** +- [Docker Desktop](https://www.docker.com/products/docker-desktop) +- [VS Code](https://code.visualstudio.com/) +- [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + +**Steps:** + +1. **Clone the repository**: + ```bash + git clone + cd opsctl + ``` + +2. **Open in VS Code**: + ```bash + code . + ``` + +3. **Reopen in Container**: + - Click "Reopen in Container" when prompted + - Or press `F1` and select "Dev Containers: Reopen in Container" + +4. **Start developing**: + The container will automatically install all dependencies via the `postCreateCommand`. Once ready: + ```bash + make test # Run tests + make run-cli # Run the CLI + make run-api # Start the API + ``` + +**Benefits:** +- ✅ Works identically on macOS, Linux, and Windows +- ✅ No need to install Python or uv locally +- ✅ Pre-configured VS Code extensions and settings +- ✅ Isolated environment per project +- ✅ Automatic port forwarding + +### Option 2: Local Setup + +1. **Install uv** (if not already installed): + + **macOS/Linux:** + ```bash + pip install uv + ``` + + Or using the official installer: + ```bash + curl -LsSf https://astral.sh/uv/install.sh | sh + ``` + + **Windows:** + ```powershell + pip install uv + ``` + + Or using PowerShell with the official installer: + ```powershell + powershell -c "irm https://astral.sh/uv/install.ps1 | iex" + ``` + +2. **Clone the repository**: + ```bash + git clone + cd opsctl + ``` + +3. **Install dependencies**: + ```bash + uv sync + ``` + +This will create a virtual environment at `.venv/` and install all dependencies. + +## Development Workflow + +### Running the CLI + +**macOS/Linux:** +```bash +# Activate the virtual environment (optional, uv run will handle this) +source .venv/bin/activate + +# Run the CLI +uv run opsctl --help +uv run opsctl hello +uv run opsctl hello "Your Name" +uv run opsctl version +``` + +**Windows (PowerShell):** +```powershell +# Activate the virtual environment (optional, uv run will handle this) +.venv\Scripts\Activate.ps1 + +# Run the CLI +uv run opsctl --help +uv run opsctl hello +uv run opsctl hello "Your Name" +uv run opsctl version +``` + +**Windows (Command Prompt):** +```cmd +# Activate the virtual environment (optional, uv run will handle this) +.venv\Scripts\activate.bat + +# Run the CLI +uv run opsctl --help +uv run opsctl hello +uv run opsctl hello "Your Name" +uv run opsctl version +``` + +### Running the API + +Start the API server in development mode with auto-reload: + +```bash +uv run uvicorn opsctl_api.main:app --reload +``` + +The API will be available at: +- Swagger UI: http://localhost:8000/docs +- ReDoc: http://localhost:8000/redoc +- OpenAPI JSON: http://localhost:8000/openapi.json + +### Testing + +Run all tests: +```bash +uv run pytest +``` + +Run tests with coverage: +```bash +uv run pytest --cov=packages --cov-report=html +``` + +Run tests for a specific package: +```bash +cd packages/cli && uv run pytest +cd packages/api && uv run pytest +``` + +### Linting and Formatting + +Check code with ruff: +```bash +uv run ruff check . +``` + +Auto-fix linting issues: +```bash +uv run ruff check --fix . +``` + +Format code: +```bash +uv run ruff format . +``` + +### Type Checking + +Run mypy for type checking: +```bash +uv run mypy packages/cli/src packages/api/src +``` + +## Project Structure + +``` +opsctl/ +├── packages/ +│ ├── api/ # FastAPI application +│ │ ├── src/ +│ │ │ └── opsctl_api/ # API source code +│ │ │ ├── __init__.py +│ │ │ └── main.py # FastAPI app +│ │ ├── tests/ # API tests +│ │ │ ├── __init__.py +│ │ │ └── test_api.py +│ │ ├── pyproject.toml # API package config +│ │ └── README.md +│ └── cli/ # Typer CLI application +│ ├── src/ +│ │ └── opsctl_cli/ # CLI source code +│ │ ├── __init__.py +│ │ └── main.py # Typer app +│ ├── tests/ # CLI tests +│ │ ├── __init__.py +│ │ └── test_cli.py +│ ├── pyproject.toml # CLI package config +│ └── README.md +├── pyproject.toml # Workspace configuration +├── .gitignore +├── .python-version # Python version +└── README.md +``` + +## Adding Dependencies + +### Workspace (dev) dependencies + +Edit `pyproject.toml` at the root and add to the `[dependency-groups]` section: + +```toml +[dependency-groups] +dev = [ + "pytest>=8.0.0", + "new-package>=1.0.0", # Add new dev dependency here +] +``` + +Then run: +```bash +uv sync +``` + +### Package-specific dependencies + +Edit the respective `pyproject.toml` in `packages/cli/` or `packages/api/`: + +```toml +[project] +dependencies = [ + "existing-package>=1.0.0", + "new-package>=2.0.0", # Add new dependency here +] +``` + +Then run: +```bash +uv sync +``` + +## Creating New Commands (CLI) + +Add new commands in `packages/cli/src/opsctl_cli/main.py`: + +```python +@app.command() +def new_command(arg: str) -> None: + """Description of your new command.""" + console.print(f"[bold]You passed: {arg}[/bold]") +``` + +## Creating New Endpoints (API) + +Add new endpoints in `packages/api/src/opsctl_api/main.py`: + +```python +@app.get("/new-endpoint") +async def new_endpoint() -> dict[str, str]: + """Description of your endpoint.""" + return {"message": "Response from new endpoint"} +``` + +## Best Practices + +1. **Write Tests**: Always add tests for new functionality +2. **Type Hints**: Use type hints for all functions +3. **Documentation**: Add docstrings to functions and classes +4. **Linting**: Run ruff before committing +5. **Type Checking**: Run mypy to catch type errors early +6. **Commit Often**: Make small, focused commits + +## Troubleshooting + +### Virtual Environment Issues + +If you encounter issues with the virtual environment: + +**macOS/Linux:** +```bash +# Remove the virtual environment +rm -rf .venv + +# Recreate it +uv sync +``` + +**Windows (PowerShell):** +```powershell +# Remove the virtual environment +Remove-Item -Recurse -Force .venv + +# Recreate it +uv sync +``` + +### Import Errors + +Make sure you're running commands with `uv run` or have activated the virtual environment: + +**macOS/Linux:** +```bash +source .venv/bin/activate +``` + +**Windows (PowerShell):** +```powershell +.venv\Scripts\Activate.ps1 +``` + +**Windows (Command Prompt):** +```cmd +.venv\Scripts\activate.bat +``` + +### Port Already in Use + +If the API server fails to start because port 8000 is in use: + +```bash +# Use a different port +uv run uvicorn opsctl_api.main:app --reload --port 8001 +``` + +## Resources + +- [uv Documentation](https://docs.astral.sh/uv/) +- [Typer Documentation](https://typer.tiangolo.com/) +- [FastAPI Documentation](https://fastapi.tiangolo.com/) +- [pytest Documentation](https://docs.pytest.org/) +- [ruff Documentation](https://docs.astral.sh/ruff/) diff --git a/Dockerfile.api b/Dockerfile.api new file mode 100644 index 0000000..cba3888 --- /dev/null +++ b/Dockerfile.api @@ -0,0 +1,20 @@ +# Dockerfile for API +FROM python:3.12-slim + +WORKDIR /app + +# Install uv +RUN pip install uv + +# Copy workspace files +COPY pyproject.toml . +COPY packages/api packages/api + +# Install dependencies +RUN uv sync + +# Expose port +EXPOSE 8000 + +# Run the API +CMD ["uv", "run", "uvicorn", "opsctl_api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d894b0d --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +.PHONY: help install test lint format typecheck clean run-cli run-api all + +help: ## Show this help message + @echo 'Usage: make [target]' + @echo '' + @echo 'Available targets:' + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +install: ## Install dependencies + uv sync + +test: ## Run all tests + uv run pytest -v + +test-cov: ## Run tests with coverage + uv run pytest --cov=packages --cov-report=html --cov-report=term + +lint: ## Run linting checks + uv run ruff check . + +lint-fix: ## Fix linting issues automatically + uv run ruff check --fix . + +format: ## Format code with ruff + uv run ruff format . + +typecheck: ## Run type checking with mypy + uv run mypy packages/cli/src packages/api/src + +clean: ## Clean up cache and build artifacts + rm -rf .venv + rm -rf .pytest_cache + rm -rf .mypy_cache + rm -rf .ruff_cache + rm -rf htmlcov + find . -type d -name __pycache__ -exec rm -rf {} + + find . -type f -name "*.pyc" -delete + +run-cli: ## Run the CLI + uv run opsctl --help + +run-api: ## Run the API server + uv run uvicorn opsctl_api.main:app --reload + +all: lint typecheck test ## Run lint, typecheck, and test diff --git a/README.md b/README.md index 0d73c72..e902b15 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,370 @@ -# opsctl -opsctl +# OpsCtl - Platform Engineering Template + +A template repository for platform engineering projects using a monorepo structure with separate CLI and API packages. + +> **🎯 Recommended**: Use the included [Dev Container](#-setup-with-dev-container-recommended) for the best cross-platform development experience! + +## 🚀 Technologies + +- **CLI**: [Typer](https://typer.tiangolo.com/) - Modern CLI framework +- **API**: [FastAPI](https://fastapi.tiangolo.com/) - High-performance web framework +- **Package Management**: [uv](https://docs.astral.sh/uv/) - Fast Python package manager +- **Testing**: [pytest](https://docs.pytest.org/) - Testing framework +- **Linting**: [ruff](https://docs.astral.sh/ruff/) - Fast Python linter +- **Type Checking**: [mypy](https://mypy-lang.org/) - Static type checker + +## 📁 Project Structure + +``` +opsctl/ +├── .devcontainer/ +│ ├── devcontainer.json # Dev Container configuration +│ ├── docker-compose.yml # Dev Container Docker Compose +│ └── Dockerfile # Dev Container Dockerfile +├── .github/ +│ └── workflows/ +│ └── ci.yml # GitHub Actions CI workflow +├── packages/ +│ ├── api/ # FastAPI application +│ │ ├── src/ +│ │ │ └── opsctl_api/ +│ │ ├── tests/ +│ │ └── pyproject.toml +│ └── cli/ # Typer CLI application +│ ├── src/ +│ │ └── opsctl_cli/ +│ ├── tests/ +│ └── pyproject.toml +├── pyproject.toml # Workspace configuration +├── Makefile # Common development tasks +├── docker-compose.yml # Docker Compose for development +├── Dockerfile.api # Dockerfile for API service +├── DEVELOPMENT.md # Detailed development guide +└── CONTRIBUTING.md # Contribution guidelines +``` + +## 🛠️ Setup with Dev Container (Recommended) + +The easiest and most consistent way to develop across all platforms (macOS, Linux, Windows). + +### Prerequisites + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) (or Docker Engine + Docker Compose) +- [VS Code](https://code.visualstudio.com/) with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + +### Quick Start + +1. Clone this repository: + ```bash + git clone + cd opsctl + ``` + +2. Open in VS Code: + ```bash + code . + ``` + +3. When prompted, click **"Reopen in Container"** (or press `F1` and select "Dev Containers: Reopen in Container") + +4. VS Code will build the container and set up the environment automatically. Once ready, you can: + ```bash + # Run tests + make test + + # Run the CLI + uv run opsctl hello + + # Start the API + make run-api + ``` + +**Benefits:** +- ✅ Identical environment across all platforms +- ✅ No local Python installation needed +- ✅ Pre-configured VS Code settings and extensions +- ✅ All dependencies pre-installed +- ✅ Port forwarding configured automatically + +## 🛠️ Local Setup (Alternative) + +If you prefer to develop locally without containers: + +### Prerequisites + +- Python 3.12+ +- uv package manager + +**Installing uv:** + +
+macOS/Linux + +```bash +pip install uv +``` + +Or using the official installer: +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` +
+ +
+Windows + +```powershell +pip install uv +``` + +Or using PowerShell with the official installer: +```powershell +powershell -c "irm https://astral.sh/uv/install.ps1 | iex" +``` +
+ +### Installation + +1. Clone this repository: + +**macOS/Linux:** +```bash +git clone +cd opsctl +``` + +**Windows:** +```powershell +git clone +cd opsctl +``` + +2. Install dependencies: + +**macOS/Linux:** +```bash +uv sync +# or +make install +``` + +**Windows (PowerShell):** +```powershell +uv sync +# Note: Makefile commands require Make for Windows or use uv directly +``` + +3. Install packages in development mode (optional): +```bash +uv pip install -e packages/cli -e packages/api +``` + +## 📝 Usage + +### CLI + +Run the CLI: + +**macOS/Linux:** +```bash +uv run opsctl hello +uv run opsctl hello World +uv run opsctl version +uv run opsctl --help +# or using make +make run-cli +``` + +**Windows (PowerShell):** +```powershell +uv run opsctl hello +uv run opsctl hello World +uv run opsctl version +uv run opsctl --help +``` + +### API + +Start the API server: + +**macOS/Linux:** +```bash +uv run uvicorn opsctl_api.main:app --reload +# or using make +make run-api +``` + +**Windows (PowerShell):** +```powershell +uv run uvicorn opsctl_api.main:app --reload +``` + +The API will be available at: +- Main: http://localhost:8000 +- Docs: http://localhost:8000/docs +- ReDoc: http://localhost:8000/redoc + +Try the endpoints: + +**macOS/Linux:** +```bash +curl http://localhost:8000/ +curl http://localhost:8000/health +curl http://localhost:8000/hello/World +``` + +**Windows (PowerShell):** +```powershell +Invoke-WebRequest http://localhost:8000/ +Invoke-WebRequest http://localhost:8000/health +Invoke-WebRequest http://localhost:8000/hello/World +# Or install curl for Windows and use curl commands +``` + +### Using Docker Compose + +Run the API with Docker Compose: +```bash +docker-compose up +``` + +## 🧪 Testing + +Run all tests: + +**macOS/Linux:** +```bash +uv run pytest +# or +make test +``` + +**Windows (PowerShell):** +```powershell +uv run pytest +``` + +Run tests with coverage: + +**macOS/Linux:** +```bash +uv run pytest --cov=packages --cov-report=html +# or +make test-cov +``` + +**Windows (PowerShell):** +```powershell +uv run pytest --cov=packages --cov-report=html +``` + +Run tests for a specific package: +```bash +uv run pytest packages/cli/tests +uv run pytest packages/api/tests +``` + +## 🔍 Linting and Type Checking + +Run ruff linter: + +**macOS/Linux:** +```bash +uv run ruff check . +# or +make lint +``` + +**Windows (PowerShell):** +```powershell +uv run ruff check . +``` + +Auto-fix linting issues: + +**macOS/Linux:** +```bash +uv run ruff check --fix . +# or +make lint-fix +``` + +**Windows (PowerShell):** +```powershell +uv run ruff check --fix . +``` + +Format code: + +**macOS/Linux:** +```bash +uv run ruff format . +# or +make format +``` + +**Windows (PowerShell):** +```powershell +uv run ruff format . +``` + +Run type checking: + +**macOS/Linux:** +```bash +uv run mypy packages/cli/src packages/api/src +# or +make typecheck +``` + +**Windows (PowerShell):** +```powershell +uv run mypy packages/cli/src packages/api/src +``` + +Run all checks: + +**macOS/Linux:** +```bash +make all +``` + +**Windows (PowerShell):** +```powershell +# Run each command separately +uv run ruff check . +uv run mypy packages/cli/src packages/api/src +uv run pytest +``` + +## 📦 Building + +Build the packages: +```bash +uv build packages/cli +uv build packages/api +``` + +Build Docker image for API: +```bash +docker build -t opsctl-api -f Dockerfile.api . +``` + +## 📚 Documentation + +- [DEVELOPMENT.md](DEVELOPMENT.md) - Detailed development guide +- [CONTRIBUTING.md](CONTRIBUTING.md) - How to contribute to this project + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Run tests and linting +5. Submit a pull request + +See [CONTRIBUTING.md](CONTRIBUTING.md) for more details. + +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/TEMPLATE_GUIDE.md b/TEMPLATE_GUIDE.md new file mode 100644 index 0000000..9146708 --- /dev/null +++ b/TEMPLATE_GUIDE.md @@ -0,0 +1,248 @@ +# Template Repository Guide + +This is a complete platform engineering template repository. Here's what's included and how to use it. + +## What's Included + +### Core Structure +- ✅ **Monorepo Layout**: Workspace with separate CLI and API packages +- ✅ **CLI Package**: Built with Typer, includes commands and tests +- ✅ **API Package**: Built with FastAPI, includes endpoints and tests +- ✅ **Package Manager**: uv for fast dependency management + +### Development Tools +- ✅ **Dev Container**: Pre-configured development container for VS Code (recommended) +- ✅ **Testing**: pytest with coverage support +- ✅ **Linting**: ruff for fast linting and formatting +- ✅ **Type Checking**: mypy for static type analysis +- ✅ **Make**: Convenient commands via Makefile +- ✅ **CI/CD**: GitHub Actions workflow included + +### Documentation +- ✅ **README.md**: Overview and quick start guide +- ✅ **DEVELOPMENT.md**: Detailed development guide +- ✅ **CONTRIBUTING.md**: Contribution guidelines +- ✅ **Package READMEs**: Individual docs for CLI and API + +### Docker Support +- ✅ **Dev Container**: Full VS Code development container setup +- ✅ **docker-compose.yml**: Quick start for development +- ✅ **Dockerfile.api**: Production-ready API container + +## Quick Start + +### Recommended: Using Dev Container + +1. **Prerequisites**: + - Install [Docker Desktop](https://www.docker.com/products/docker-desktop) + - Install [VS Code](https://code.visualstudio.com/) + - Install [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + +2. **Clone and open**: + ```bash + git clone + cd + code . + ``` + +3. **Reopen in Container**: + - Click "Reopen in Container" when prompted + - Wait for container to build and dependencies to install + +4. **Start developing**: + ```bash + make test # Run tests + make run-cli # Run CLI + make run-api # Start API + ``` + +### Alternative: Local Setup + +1. **Use this template** on GitHub to create a new repository + +2. **Clone your new repository**: + ```bash + git clone + cd + ``` + +3. **Install dependencies**: + + **macOS/Linux:** + ```bash + make install + # or + uv sync + ``` + + **Windows (PowerShell):** + ```powershell + uv sync + ``` + +4. **Run tests**: + + **macOS/Linux:** + ```bash + make test + # or + uv run pytest + ``` + + **Windows (PowerShell):** + ```powershell + uv run pytest + ``` + +5. **Try the CLI**: + + **macOS/Linux:** + ```bash + make run-cli + # or + uv run opsctl hello + ``` + + **Windows (PowerShell):** + ```powershell + uv run opsctl hello + ``` + +6. **Start the API**: + + **macOS/Linux:** + ```bash + make run-api + # or + uv run uvicorn opsctl_api.main:app --reload + ``` + + **Windows (PowerShell):** + ```powershell + uv run uvicorn opsctl_api.main:app --reload + ``` + +## Customization + +### Rename the Project + +1. Update package names in: + - `packages/cli/pyproject.toml` (name and scripts) + - `packages/api/pyproject.toml` (name) + - `packages/cli/src/opsctl_cli/` → rename directory + - `packages/api/src/opsctl_api/` → rename directory + +2. Update imports in test files + +3. Update README and documentation + +### Add New Features + +**CLI Commands**: Edit `packages/cli/src/opsctl_cli/main.py` +```python +@app.command() +def my_command() -> None: + """My new command.""" + console.print("Hello!") +``` + +**API Endpoints**: Edit `packages/api/src/opsctl_api/main.py` +```python +@app.get("/my-endpoint") +async def my_endpoint() -> dict[str, str]: + """My new endpoint.""" + return {"message": "Hello!"} +``` + +### Add Dependencies + +**Development dependencies** (testing, linting, etc.): +Edit root `pyproject.toml` → `[dependency-groups] dev` + +**CLI dependencies**: +Edit `packages/cli/pyproject.toml` → `[project] dependencies` + +**API dependencies**: +Edit `packages/api/pyproject.toml` → `[project] dependencies` + +Then run: `make install` + +## Available Commands + +**macOS/Linux (using Make):** +```bash +make help # Show all available commands +make install # Install/update dependencies +make test # Run all tests +make test-cov # Run tests with coverage report +make lint # Check code style +make lint-fix # Auto-fix code style issues +make format # Format code +make typecheck # Run type checking +make run-cli # Run the CLI +make run-api # Start the API server +make all # Run lint, typecheck, and test +make clean # Remove all cache and build files +``` + +**Windows (using uv directly):** +```powershell +uv sync # Install/update dependencies +uv run pytest # Run all tests +uv run pytest --cov=packages --cov-report=html # Run tests with coverage +uv run ruff check . # Check code style +uv run ruff check --fix . # Auto-fix code style issues +uv run ruff format . # Format code +uv run mypy packages/cli/src packages/api/src # Run type checking +uv run opsctl --help # Run the CLI +uv run uvicorn opsctl_api.main:app --reload # Start the API server +``` + +> **Note for Windows users**: The Makefile requires Make for Windows (e.g., via Chocolatey: `choco install make`) or you can use the `uv` commands directly as shown above. + +## CI/CD + +The template includes a GitHub Actions workflow (`.github/workflows/ci.yml`) that: +- Runs on push and pull requests +- Tests on Python 3.12 +- Runs linting, type checking, and tests +- Uploads coverage to Codecov + +## Docker + +**Development**: +```bash +docker-compose up +``` + +**Production**: +```bash +docker build -t my-api -f Dockerfile.api . +docker run -p 8000:8000 my-api +``` + +## Best Practices + +1. **Keep tests updated** - Add tests for all new features +2. **Run checks often** - Use `make all` before committing +3. **Type everything** - Use type hints for better code quality +4. **Document changes** - Update README and docs when needed +5. **Small commits** - Make focused, atomic commits + +## Getting Help + +- Check `DEVELOPMENT.md` for detailed development info +- Check `CONTRIBUTING.md` for contribution guidelines +- Open an issue for questions or problems + +## Next Steps + +1. Customize the template for your project +2. Add your own commands/endpoints +3. Update documentation +4. Set up your CI/CD secrets (if needed) +5. Start building! + +--- + +**Happy coding!** 🚀 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..53d1a24 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + api: + image: python:3.12-slim + working_dir: /app + volumes: + - .:/app + ports: + - "8000:8000" + command: > + sh -c "pip install uv && + uv sync && + uv run uvicorn opsctl_api.main:app --host 0.0.0.0 --reload" + environment: + - PYTHONUNBUFFERED=1 diff --git a/packages/api/README.md b/packages/api/README.md new file mode 100644 index 0000000..cf508c1 --- /dev/null +++ b/packages/api/README.md @@ -0,0 +1,17 @@ +# API Package + +This is the API package built with FastAPI. + +## Installation + +```bash +uv pip install -e packages/api +``` + +## Usage + +```bash +uvicorn opsctl_api.main:app --reload +``` + +The API will be available at http://localhost:8000 diff --git a/packages/api/pyproject.toml b/packages/api/pyproject.toml new file mode 100644 index 0000000..dcb69f4 --- /dev/null +++ b/packages/api/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "opsctl-api" +version = "0.1.0" +description = "API service built with FastAPI" +readme = "README.md" +authors = [ + { name = "Alex Lutz", email = "alex@example.com" } +] +requires-python = ">=3.12" +dependencies = [ + "fastapi>=0.109.0", + "uvicorn[standard]>=0.27.0", + "pydantic>=2.0.0", +] + +[build-system] +requires = ["uv_build>=0.9.7,<0.10.0"] +build-backend = "uv_build" diff --git a/packages/api/src/opsctl_api/__init__.py b/packages/api/src/opsctl_api/__init__.py new file mode 100644 index 0000000..4c12ec2 --- /dev/null +++ b/packages/api/src/opsctl_api/__init__.py @@ -0,0 +1,3 @@ +"""API package built with FastAPI.""" + +__version__ = "0.1.0" diff --git a/packages/api/src/opsctl_api/main.py b/packages/api/src/opsctl_api/main.py new file mode 100644 index 0000000..cf6d888 --- /dev/null +++ b/packages/api/src/opsctl_api/main.py @@ -0,0 +1,43 @@ +"""Main API application using FastAPI.""" + +from fastapi import FastAPI +from pydantic import BaseModel + +from opsctl_api import __version__ + +app = FastAPI( + title="OpsCtl API", + description="Platform engineering API service", + version=__version__, +) + + +class HealthResponse(BaseModel): + """Health check response model.""" + + status: str + version: str + + +class HelloResponse(BaseModel): + """Hello response model.""" + + message: str + + +@app.get("/") +async def root() -> dict[str, str]: + """Root endpoint.""" + return {"message": "Welcome to OpsCtl API"} + + +@app.get("/health", response_model=HealthResponse) +async def health() -> HealthResponse: + """Health check endpoint.""" + return HealthResponse(status="healthy", version=__version__) + + +@app.get("/hello/{name}", response_model=HelloResponse) +async def hello(name: str) -> HelloResponse: + """Say hello to someone.""" + return HelloResponse(message=f"Hello {name}!") diff --git a/packages/api/tests/__init__.py b/packages/api/tests/__init__.py new file mode 100644 index 0000000..4e1edc0 --- /dev/null +++ b/packages/api/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for the API package.""" diff --git a/packages/api/tests/test_api.py b/packages/api/tests/test_api.py new file mode 100644 index 0000000..b3af09e --- /dev/null +++ b/packages/api/tests/test_api.py @@ -0,0 +1,30 @@ +"""Tests for API main module.""" + +from fastapi.testclient import TestClient +from opsctl_api.main import app + +client = TestClient(app) + + +def test_root() -> None: + """Test root endpoint.""" + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"message": "Welcome to OpsCtl API"} + + +def test_health() -> None: + """Test health endpoint.""" + response = client.get("/health") + assert response.status_code == 200 + data = response.json() + assert data["status"] == "healthy" + assert data["version"] == "0.1.0" + + +def test_hello() -> None: + """Test hello endpoint.""" + response = client.get("/hello/World") + assert response.status_code == 200 + data = response.json() + assert data["message"] == "Hello World!" diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..e88d920 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,15 @@ +# CLI Package + +This is the CLI package built with Typer. + +## Installation + +```bash +uv pip install -e packages/cli +``` + +## Usage + +```bash +opsctl --help +``` diff --git a/packages/cli/pyproject.toml b/packages/cli/pyproject.toml new file mode 100644 index 0000000..d640db2 --- /dev/null +++ b/packages/cli/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "opsctl-cli" +version = "0.1.0" +description = "CLI tool built with Typer" +readme = "README.md" +authors = [ + { name = "Alex Lutz", email = "alex@example.com" } +] +requires-python = ">=3.12" +dependencies = [ + "typer>=0.9.0", + "rich>=13.0.0", +] + +[project.scripts] +opsctl = "opsctl_cli.main:app" + +[build-system] +requires = ["uv_build>=0.9.7,<0.10.0"] +build-backend = "uv_build" diff --git a/packages/cli/src/opsctl_cli/__init__.py b/packages/cli/src/opsctl_cli/__init__.py new file mode 100644 index 0000000..3b07252 --- /dev/null +++ b/packages/cli/src/opsctl_cli/__init__.py @@ -0,0 +1,3 @@ +"""CLI package built with Typer.""" + +__version__ = "0.1.0" diff --git a/packages/cli/src/opsctl_cli/main.py b/packages/cli/src/opsctl_cli/main.py new file mode 100644 index 0000000..e6adef7 --- /dev/null +++ b/packages/cli/src/opsctl_cli/main.py @@ -0,0 +1,32 @@ +"""Main CLI application using Typer.""" + +import typer +from rich.console import Console + +app = typer.Typer( + name="opsctl", + help="Platform engineering CLI tool", + add_completion=True, +) +console = Console() + + +@app.command() +def hello(name: str | None = typer.Argument(None, help="Name to greet")) -> None: + """Say hello to someone.""" + if name: + console.print(f"[bold green]Hello {name}![/bold green]") + else: + console.print("[bold green]Hello from opsctl![/bold green]") + + +@app.command() +def version() -> None: + """Show the version.""" + from opsctl_cli import __version__ + + console.print(f"[bold blue]opsctl version:[/bold blue] {__version__}") + + +if __name__ == "__main__": + app() diff --git a/packages/cli/tests/__init__.py b/packages/cli/tests/__init__.py new file mode 100644 index 0000000..edf17b9 --- /dev/null +++ b/packages/cli/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for the CLI package.""" diff --git a/packages/cli/tests/test_cli.py b/packages/cli/tests/test_cli.py new file mode 100644 index 0000000..ca66b0d --- /dev/null +++ b/packages/cli/tests/test_cli.py @@ -0,0 +1,28 @@ +"""Tests for CLI main module.""" + +from opsctl_cli.main import app +from typer.testing import CliRunner + +runner = CliRunner() + + +def test_hello_without_name() -> None: + """Test hello command without a name.""" + result = runner.invoke(app, ["hello"]) + assert result.exit_code == 0 + assert "Hello from opsctl!" in result.stdout + + +def test_hello_with_name() -> None: + """Test hello command with a name.""" + result = runner.invoke(app, ["hello", "World"]) + assert result.exit_code == 0 + assert "Hello World!" in result.stdout + + +def test_version() -> None: + """Test version command.""" + result = runner.invoke(app, ["version"]) + assert result.exit_code == 0 + assert "opsctl version:" in result.stdout + assert "0.1.0" in result.stdout diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..abedffc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[tool.uv.workspace] +members = ["packages/cli", "packages/api"] + +[dependency-groups] +dev = [ + "pytest>=8.0.0", + "pytest-cov>=4.1.0", + "ruff>=0.3.0", + "mypy>=1.8.0", + "httpx>=0.27.0", +] + +[tool.pytest.ini_options] +testpaths = ["packages/cli/tests", "packages/api/tests"] +python_files = "test_*.py" +python_classes = "Test*" +python_functions = "test_*" +addopts = "-v --tb=short --strict-markers --import-mode=importlib" + +[tool.ruff] +line-length = 100 +target-version = "py312" + +[tool.ruff.lint] +select = ["E", "F", "I", "N", "W", "B", "UP"] +ignore = [] + +[tool.mypy] +python_version = "3.12" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true