Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 31 additions & 23 deletions .ai/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,64 @@ This file provides guidance to AI coding assistants when working with code in th

## Project Overview

tools-api is a FastAPI service providing internal REST APIs for GlueOps platform engineers. It manages AWS accounts, cloud storage (MinIO), Hetzner infrastructure (Chisel load balancers), GitHub organization setup, Kubernetes/ArgoCD manifest generation, and Opsgenie alerting.
tools-api is a Go API service (using the Huma framework on Chi router) providing internal REST APIs for GlueOps platform engineers. It manages AWS accounts, cloud storage (MinIO), Hetzner infrastructure (Chisel load balancers), GitHub organization setup, Kubernetes/ArgoCD manifest generation, and Opsgenie alerting.

A companion Go CLI (`cli/`) allows engineers to interact with the API from headless Linux machines. See [`cli/.ai/AGENTS.md`](../cli/.ai/AGENTS.md) for CLI-specific guidance.

## Development Setup

```bash
# Enter development shell (launches pipenv shell via devbox)
devbox run dev
All builds use Docker — no local Go toolchain is required.

# Install dependencies
pipenv install
```bash
# Build the server
docker build -t tools-api .

# Run dev server (hot reload)
fastapi dev
# Run the server
docker run --rm -p 8000:8000 tools-api

# Run production server
fastapi run
# Run Go commands via Docker
docker run --rm -v "$(pwd):/app" -w /app golang:1.24-alpine go build ./...
docker run --rm -v "$(pwd):/app" -w /app golang:1.24-alpine go test ./...
```

Required environment variables: `AWS_GLUEOPS_ROCKS_ORG_ACCESS_KEY`, `AWS_GLUEOPS_ROCKS_ORG_SECRET_KEY`, `HCLOUD_TOKEN`, `GITHUB_TOKEN`, `MINIO_S3_ACCESS_KEY_ID`, `MINIO_S3_SECRET_KEY`, `HETZNER_STORAGE_REGION=hel1`.
Required environment variables: `AWS_GLUEOPS_ROCKS_ORG_ACCESS_KEY`, `AWS_GLUEOPS_ROCKS_ORG_SECRET_KEY`, `HCLOUD_TOKEN`, `GITHUB_TOKEN`, `MINIO_S3_ACCESS_KEY_ID`, `MINIO_S3_SECRET_KEY`, `HETZNER_STORAGE_REGION`. Optional: `LOG_LEVEL` (default `INFO`).

Environment variables are NOT required at startup — the app fails lazily when an endpoint is called without the needed env var.

## Build

```bash
docker build -t tools-api .
```

The Dockerfile uses `python:3.14-slim` as base, installs dependencies via pipenv (`--system`), and accepts build args: `VERSION`, `COMMIT_SHA`, `SHORT_SHA`, `BUILD_TIMESTAMP`, `GIT_REF`. Devbox is used for local development only (Python 3.13 via nixpkgs), not in container builds.
The Dockerfile uses a multi-stage build: `golang:1.24-alpine` for compilation, `alpine:3.21` for runtime. Build args: `VERSION`, `COMMIT_SHA`, `SHORT_SHA`, `BUILD_TIMESTAMP`, `GIT_REF` (injected via ldflags into `internal/version`).

## Architecture

- **`app/main.py`** — FastAPI app entry point. Defines all API routes, global exception handler, health/version endpoints. Routes redirect `/` to `/docs`.
- **`app/schemas/schemas.py`** — Pydantic request/response models for all endpoints (including `VersionResponse` for `/version`). Examples and descriptions defined here are the single source of truth — the CLI reads them from the embedded OpenAPI spec at compile time.
- **`app/util/`** — Business logic modules, one per domain: `storage.py` (MinIO), `github.py`, `hetzner.py`, `aws_setup_test_account_credentials.py`, `chisel.py`, `captain_manifests.py`, `opsgenie.py`.
- **`app/templates/captain_manifests/`** — Jinja2 templates (`.yaml.j2`) for generating Kubernetes manifests (Namespace, AppProject, ApplicationSet).
- **`cmd/server/main.go`** — Go API server entry point. Uses Huma framework on Chi router. Defines all API routes, audit logging middleware, custom error handling, graceful shutdown (SIGTERM/SIGINT). Health endpoint (`/health`) and root redirect (`/ → /docs`) are registered directly on Chi (excluded from OpenAPI).
- **`pkg/handlers/`** — HTTP handler functions for each domain: `storage.go`, `aws.go`, `github.go`, `chisel.go`, `opsgenie.go`, `captain.go`, `health.go`, `version.go`.
- **`pkg/types/`** — Shared request/response type definitions (`types.go`). These are the single source of truth for API contracts.
- **`pkg/`** — Business logic modules, one per domain: `storage/`, `aws/`, `github/`, `hetzner/`, `chisel/`, `captain/`, `opsgenie/`.
- **`pkg/util/`** — Utility functions (e.g., `plaintext.go` for plain-text response helpers).
- **`internal/version/`** — Build-time injected version variables (ldflags).
- **`cli/`** — Go CLI binary. See [`cli/.ai/AGENTS.md`](../cli/.ai/AGENTS.md).

All routes are defined directly in `main.py` (no router separation). Each route delegates to a corresponding util module.
### Key Design Decisions

GitHub workflow endpoints (`github.py`) dispatch workflows via the GitHub API and poll for the resulting run ID. They return JSON with `status_code`, `all_jobs_url`, `run_id`, and `run_url`. A separate `/v1/github/workflow-run-status` endpoint accepts any GitHub Actions run URL and returns its current status. All GitHub API calls use a centralized `_get_headers()` with the `X-GitHub-Api-Version` header.
- **Plain-text endpoints** — Five endpoints return `Content-Type: text/plain` (storage buckets, AWS credentials, chisel, opsgenie manifest, captain manifests). These use custom Huma response handling to avoid JSON wrapping.
- **Error responses** — Custom error format `{"status": N, "detail": "..."}` via `huma.NewError` override. Stack traces logged server-side only, never in responses.
- **Graceful shutdown** — Handles SIGTERM/SIGINT with 25-second timeout for in-flight requests.
- **Audit logging** — Middleware logs every request with `X-Forwarded-User` and `X-Forwarded-Email` from oauth2-proxy.

## Key Dependencies

- **`glueops-helpers`** — Internal library (installed from GitHub) providing `setup_logging` and shared utilities.
- **`minio`** — S3-compatible storage client.
- **`boto3`** — AWS SDK (account credential management via STS/Organizations).
- **`hcloud`** — Hetzner Cloud API client (Chisel node provisioning).
- **`github.com/danielgtaylor/huma/v2`** — API framework (OpenAPI 3.1, validation, docs).
- **`github.com/go-chi/chi/v5`** — HTTP router.
- **`github.com/aws/aws-sdk-go-v2`** — AWS SDK (account credential management via STS/Organizations).
- **`github.com/hetznercloud/hcloud-go/v2`** — Hetzner Cloud API client.
- **`github.com/minio/minio-go/v7`** — S3-compatible storage client.

## CI/CD

- **`.github/workflows/container_image.yaml`** — Builds and pushes Docker images to GHCR on any push.
- **`.github/workflows/container_image.yaml`** — Runs golangci-lint and govulncheck, then builds and pushes Docker images to GHCR on any push.
- **`.github/workflows/cli_release.yaml`** — Builds CLI binaries on every push, uploads as workflow artifacts, and creates a GitHub Release tagged with `github.ref_name`. Cross-compiles for linux/amd64, linux/arm64, darwin/amd64, darwin/arm64.
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.git/
cli/
tickets/
*.md
.ai/
deployment-configurations/
4 changes: 4 additions & 0 deletions .github/workflows/cli_release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- name: Set build variables
id: vars
Expand Down Expand Up @@ -42,11 +44,13 @@ jobs:
cli/tools-darwin-arm64

- name: Delete existing release for this ref
if: startsWith(github.ref, 'refs/tags/v')
run: gh release delete "${{ github.ref_name }}" --yes 2>/dev/null || true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@1853d73993c8ca1b2c9c1a7fede39682d0ab5c2a # v2.5.3
with:
tag_name: ${{ github.ref_name }}
Expand Down
34 changes: 28 additions & 6 deletions .github/workflows/container_image.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Publish to GHCR.io

on:
push:
workflow_dispatch:
Expand All @@ -9,7 +9,31 @@ env:
IMAGE_NAME: ${{ github.repository }}

jobs:
lint-and-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod

- name: Run golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
with:
version: latest

- name: Run govulncheck
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1
with:
go-version-file: go.mod

build_tag_push_to_ghcr:
needs: lint-and-scan
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -19,10 +43,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6


- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
with:
persist-credentials: false

- name: Setup Docker buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
Expand Down Expand Up @@ -81,4 +103,4 @@ jobs:
exclude-tags: latest,main,v*
keep-n-tagged: 5
delete-untagged: true
delete-partial-images: true
delete-partial-images: true
37 changes: 20 additions & 17 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
FROM python:3.14-slim
# Build stage
FROM golang:1.24-alpine AS builder

WORKDIR /code

# Accept build arguments for versioning
ARG VERSION=UNKNOWN
ARG COMMIT_SHA=UNKNOWN
ARG SHORT_SHA=UNKNOWN
ARG BUILD_TIMESTAMP=UNKNOWN
ARG GIT_REF=UNKNOWN

ENV VERSION=${VERSION}
ENV COMMIT_SHA=${COMMIT_SHA}
ENV SHORT_SHA=${SHORT_SHA}
ENV BUILD_TIMESTAMP=${BUILD_TIMESTAMP}
ENV GIT_REF=${GIT_REF}

RUN pip install --no-cache-dir pipenv

COPY Pipfile Pipfile.lock ./
RUN pipenv install --system

COPY app/ /code/app
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build \
-ldflags="-s -w \
-X github.com/GlueOps/tools-api/internal/version.Version=${VERSION} \
-X github.com/GlueOps/tools-api/internal/version.CommitSHA=${COMMIT_SHA} \
-X github.com/GlueOps/tools-api/internal/version.ShortSHA=${SHORT_SHA} \
-X github.com/GlueOps/tools-api/internal/version.BuildTimestamp=${BUILD_TIMESTAMP} \
-X github.com/GlueOps/tools-api/internal/version.GitRef=${GIT_REF}" \
-o /server ./cmd/server

CMD ["fastapi", "run"]
# Runtime stage
FROM alpine:3.21
RUN apk add --no-cache ca-certificates
COPY --from=builder /server /server
EXPOSE 8000
ENTRYPOINT ["/server"]
16 changes: 0 additions & 16 deletions Pipfile

This file was deleted.

Loading
Loading