From a75436690125a3bdafae6b2cfc86ad00be0b2fbf Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Thu, 15 Oct 2026 00:52:38 +0530 Subject: [PATCH] feat(standards): add Drone CI standard (PyPI package + Docker deployment) Document the two `.drone.yml` shapes used across the org, derived from the repos: - Type A `python-lib.yaml`: build-and-publish-pypi + backfill-pypi-from-tags - Type B `service-docker.yaml`: build-and-deploy-service + build-and-deploy-site - add reference page `10_drone_pipelines` - vendor `src/templates/drone/` into the MCP bundle (`drone.json`) - expose `registry://drone` + `list_drone_templates` / `get_drone_template` - wire `build.py` to vendor drone templates; rebuild nav/index (14 modules) --- build.py | 77 ++++++ registry.py | 73 +++++ standards/mcp/drone.json | 22 ++ standards/mcp/index.json | 2 +- standards/mcp/modules/drone_pipelines.json | 8 + standards/mcp/modules/index.json | 2 +- standards/mcp/nav.json | 5 + standards/mcp/templates/drone/python-lib.yaml | 164 +++++++++++ .../mcp/templates/drone/service-docker.yaml | 257 ++++++++++++++++++ standards/src/lib/10_drone_pipelines.md | 102 +++++++ standards/src/lib/index.md | 1 + standards/src/mkdocs.lib.yml | 1 + standards/src/templates/drone/python-lib.yaml | 164 +++++++++++ .../src/templates/drone/service-docker.yaml | 257 ++++++++++++++++++ 14 files changed, 1133 insertions(+), 2 deletions(-) create mode 100644 standards/mcp/drone.json create mode 100644 standards/mcp/modules/drone_pipelines.json create mode 100644 standards/mcp/templates/drone/python-lib.yaml create mode 100644 standards/mcp/templates/drone/service-docker.yaml create mode 100644 standards/src/lib/10_drone_pipelines.md create mode 100644 standards/src/templates/drone/python-lib.yaml create mode 100644 standards/src/templates/drone/service-docker.yaml diff --git a/build.py b/build.py index 607077b..3e678b2 100644 --- a/build.py +++ b/build.py @@ -336,6 +336,82 @@ def build_templates(folder: Path, dry_run: bool = False) -> None: f"({len(manifest['components'])} components)") +# ── Drone standard bundle ───────────────────────────────────────────────────── + +# The two CI shapes shipped in ``src/templates/drone``. There are exactly two +# types of ``.drone.yml`` in the org: a Python package (private PyPI) and a +# Docker deployment (build / push / run). See standards "10_drone_pipelines". +_DRONE_COMPONENTS: list[dict[str, str]] = [ + {"kind": "drone-pypi", "path": "python-lib.yaml", "placeholder": "", + "description": "Drone pipeline: build + publish a Python package to the private PyPI index."}, + {"kind": "drone-service", "path": "service-docker.yaml", "placeholder": "", + "description": "Drone pipeline: build, push, and deploy a Docker service or site."}, +] + + +def build_drone_manifest(template_root: Path) -> dict[str, object]: + """Describe every CI template in the vendored drone standard. + + Args: + template_root (Path): The vendored ``templates/drone`` directory. + + Returns: + dict[str, object]: Manifest with the two component entries. + """ + components: list[dict[str, str]] = [] + for entry in _DRONE_COMPONENTS: + rel = entry["path"] + if not (template_root / rel).exists(): + raise SystemExit(f"[ERR] drone template missing: {rel}") + components.append({ + "kind": entry["kind"], + "path": rel, + "placeholder": entry["placeholder"], + "description": entry["description"], + }) + return { + "project": "standards", + "template": "templates/drone", + "types": ["pypi", "docker"], + "components": components, + } + + +def build_drone_templates(folder: Path, dry_run: bool = False) -> None: + """Vendor ``folder/src/templates/drone`` into ``/mcp/templates/drone``. + + Ships the two ``.drone.yml`` shapes (PyPI package, Docker deployment) in the + MCP bundle so an agent can pull the exact CI file, and emits ``drone.json`` + describing them. + + Args: + folder (Path): The hub folder owning ``src/templates/drone``. + dry_run (bool): Print the plan without executing. + """ + name = folder.name + src = folder / "src" / "templates" / "drone" + if not src.is_dir(): + return + out_dir = folder / "mcp" / "templates" / "drone" + if dry_run: + print(f"[DRN] {name} {src.relative_to(ROOT)}/** -> {out_dir.relative_to(ROOT)}/**") + return + + if out_dir.exists(): + shutil.rmtree(out_dir) + out_dir.parent.mkdir(parents=True, exist_ok=True) + shutil.copytree( + src, out_dir, + ignore=shutil.ignore_patterns("__pycache__", "*.pyc", ".git"), + ) + manifest = build_drone_manifest(out_dir) + (folder / "mcp" / "drone.json").write_text( + json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8" + ) + print(f"[DRN] {name} drone templates vendored to {out_dir.relative_to(ROOT)}/ " + f"({len(manifest['components'])} components)") + + # ── CLI ────────────────────────────────────────────────────────────────────── @@ -355,6 +431,7 @@ def main() -> None: for folder in folders: build_bundle(folder, dry_run=args.dry_run) build_templates(folder, dry_run=args.dry_run) + build_drone_templates(folder, dry_run=args.dry_run) build_html(folder, dry_run=args.dry_run) diff --git a/registry.py b/registry.py index 91ed39f..520abc7 100644 --- a/registry.py +++ b/registry.py @@ -10,6 +10,7 @@ Resources: docs://spec (the pinned auth-server OpenAPI contract) registry://libraries (the structured catalog) registry://components (the template component index) + registry://drone (the CI template index) Tools: ping (from the base server) @@ -17,6 +18,8 @@ Tools: list_app_components (the template component kinds) get_app_component (kind -> exact template file, seed tokens renamed) get_app_template (the whole app skeleton as a path -> content map) + list_drone_templates (the two CI shapes: PyPI package / Docker deployment) + get_drone_template (kind -> exact .drone.yml template) Prompts: intake (the questions to gather before building) @@ -126,6 +129,32 @@ class StandardsMCPServer(MCPServer): return {"error": "not_found", "path": str(path)} return json.loads(path.read_bytes()) + def _drone(self) -> dict[str, Any]: + """Load the CI (Drone) template manifest.""" + path = Path(self.mcp_root) / "drone.json" + if not path.exists(): + return {"error": "not_found", "path": str(path), "components": []} + return json.loads(path.read_bytes()) + + def _find_drone_component(self, kind: str) -> dict[str, Any] | None: + for entry in self._drone().get("components", []): + if entry.get("kind") == kind: + return entry + return None + + def _read_drone_component(self, entry: dict[str, Any]) -> dict[str, Any]: + """Read a vendored drone template file.""" + rel = entry["path"] + path = Path(self.mcp_root) / "templates" / "drone" / rel + if not path.exists(): + return {"error": "not_found", "path": str(path)} + return { + "kind": entry["kind"], + "path": rel, + "placeholder": entry.get("placeholder"), + "content": path.read_text(encoding="utf-8"), + } + def _find_component(self, kind: str) -> dict[str, Any] | None: for entry in self._components().get("components", []): if entry.get("kind") == kind: @@ -195,6 +224,10 @@ class StandardsMCPServer(MCPServer): def components() -> dict[str, Any]: return self._components() + @self.app.resource("registry://drone") + def drone() -> dict[str, Any]: + return self._drone() + @self.app.resource("docs://spec") def spec() -> dict[str, Any]: """Return the pinned auth-server OpenAPI contract. @@ -307,6 +340,46 @@ class StandardsMCPServer(MCPServer): "files": files, } + @self.app.tool() + def list_drone_templates() -> list[dict[str, Any]]: + """List the CI (Drone) template shapes. + + There are exactly two types of ``.drone.yml`` in the org: a Python + package published to the private PyPI index, and a Docker + deployment that builds, pushes, and runs a container. + + Returns: + list[dict[str, Any]]: + Entries with ``kind``, ``path``, ``placeholder`` and + ``description``. Fetch one with :func:`get_drone_template`. + """ + return self._drone().get("components", []) + + @self.app.tool() + def get_drone_template(kind: str) -> dict[str, Any]: + """Return the exact CI template for a component kind. + + Use this to copy the current ``.drone.yml`` shape instead of + inventing one. See :func:`list_drone_templates` for valid kinds + ("drone-pypi", "drone-service"). + + Args: + kind (str): Component kind, e.g. "drone-pypi" or + "drone-service". + + Returns: + dict[str, Any]: ``{kind, path, placeholder, content}`` or an + error entry when the kind is unknown. + """ + entry = self._find_drone_component(kind) + if entry is None: + return { + "error": "unknown_kind", + "kind": kind, + "available": [c["kind"] for c in self._drone().get("components", [])], + } + return self._read_drone_component(entry) + @self.app.prompt() def intake() -> str: """Return the questions to gather before building an app. diff --git a/standards/mcp/drone.json b/standards/mcp/drone.json new file mode 100644 index 0000000..552c8f2 --- /dev/null +++ b/standards/mcp/drone.json @@ -0,0 +1,22 @@ +{ + "project": "standards", + "template": "templates/drone", + "types": [ + "pypi", + "docker" + ], + "components": [ + { + "kind": "drone-pypi", + "path": "python-lib.yaml", + "placeholder": "", + "description": "Drone pipeline: build + publish a Python package to the private PyPI index." + }, + { + "kind": "drone-service", + "path": "service-docker.yaml", + "placeholder": "", + "description": "Drone pipeline: build, push, and deploy a Docker service or site." + } + ] +} \ No newline at end of file diff --git a/standards/mcp/index.json b/standards/mcp/index.json index e2413a7..0ec1ff3 100644 --- a/standards/mcp/index.json +++ b/standards/mcp/index.json @@ -1,6 +1,6 @@ { "project": "standards", "type": "standards", - "modules_count": 13, + "modules_count": 14, "source": "hand-authored" } \ No newline at end of file diff --git a/standards/mcp/modules/drone_pipelines.json b/standards/mcp/modules/drone_pipelines.json new file mode 100644 index 0000000..acd5788 --- /dev/null +++ b/standards/mcp/modules/drone_pipelines.json @@ -0,0 +1,8 @@ +{ + "module": "drone_pipelines", + "content": { + "path": "drone_pipelines", + "docstring": "# Drone Pipelines\n\n## Summary\n\nEvery Aetos repository ships a Drone CI definition at `.drone.yml`. There is\nexactly **one standard per repository**, and there are **two types of file**,\nchosen by what the repository produces:\n\n| Type | File | Produces | Repos |\n|------|------|----------|-------|\n| **A — PyPI package** | `templates/drone/python-lib.yaml` | A wheel + sdist on the private index | `dagpipe`, `doc-forge`, `hexa`, `mail-intake`, `mongo-ops`, `omniread`, `openapi-first`, `py-jwt` |\n| **B — Docker deployment** | `templates/drone/service-docker.yaml` | An image pushed to the registry and run as a container | `auth-server`, `docs`, `mcp` |\n\nBoth templates run on `platform: linux / arm64` with the workspace at\n`/drone/src`. Copy the matching template to `/.drone.yml`; the Type A file\nneeds no edits, the Type B file needs its placeholders filled in (below).\n\n> Conventions: `.drone.yml` (not `.yaml`), one file per repo, secrets injected\n> with `from_secret` — never commit credentials.\n\n---\n\n## Type A — PyPI package (`python-lib.yaml`)\n\nFor internal libraries. The file contains **two pipelines**:\n\n1. **`build-and-publish-pypi`** — triggered on `tag`:\n - `check-version` — read `name` / `version` from `pyproject.toml` and query\n `$PIP_REPO_URL/simple//`; if that version already exists, exit\n `78` and skip the whole build.\n - `quality-gate` — install the package with its `[dev]` extras from the\n private index, then `black --check`, `ruff check`, `mypy`, `pydoclint`,\n `pytest`.\n - `build-package` — `python -m build` into `dist/`.\n - `upload-to-private-pypi` — `twine upload` to `$PIP_REPO_URL`.\n2. **`backfill-pypi-from-tags`** — triggered on `custom`:\n - `fetch-tags` — `git fetch --tags --force`.\n - `build-and-upload-missing` — walk every tag in version order, skip the ones\n already on the index, `git checkout` the rest, build, and upload. This is\n the disaster-recovery / replay path.\n\nSecrets: `PIP_REPO_URL`, `PIP_USERNAME`, `PIP_PASSWORD`.\n\n- **Ship as-is.** Package name and version come from `pyproject.toml`, so the\n same file is reused verbatim across every library.\n\n---\n\n## Type B — Docker deployment (`service-docker.yaml`)\n\nFor services and sites that ship a container image. The file contains **two\npipelines**, each mirroring a repo family:\n\n1. **`build-and-deploy-service`** — triggered on `tag` (a long-running\n API/worker service, e.g. `auth-server`):\n - `fetch-tags` — resolve the latest tag into `LATEST_TAG.txt`; fail if there\n are no tags.\n - `check-remote-image` — if `aetos/:` already exists locally, exit\n `78` and skip the build.\n - `quality-gate` — install from `requirements.txt` and run\n `black` / `ruff` / `mypy` / `pytest`.\n - `build-image` — `docker build` with the private-index build args, tagged\n `aetos/:` and `:latest`.\n - `push-image` — login, re-tag with `$REGISTRY_HOST`, push both tags.\n - `stop-old` — `docker rm -f `.\n - `run-container` — `docker run -d --name -p :\n --restart always` with the service's `-e` environment.\n2. **`build-and-deploy-site`** — triggered on `push` (a static/HTTP site, e.g.\n `docs`, `mcp`): `build-image` → `push-image` → `stop-old` → `run-container`,\n with no tag resolution and no quality gate.\n\nSecrets: `PIP_REPO_URL`, `PIP_USERNAME`, `PIP_PASSWORD` (service build args),\n`REGISTRY_HOST`, `REGISTRY_USER`, `REGISTRY_PASS`, plus the service runtime env\n(for example `MONGO_*`, `JWT_SECRET`).\n\nPlaceholders to fill in per repo:\n\n| Placeholder | Meaning | Example |\n|-------------|---------|---------|\n| `` | service image / container name | `auth-server` |\n| `aetos/` | image repository on the registry | `aetos/auth-server` |\n| `` / `` | published : in-image port | `9003:8000` |\n| `` | site image / container name | `docs` |\n| `` | runtime environment variable | `MONGO_HOST` |\n\n---\n\n## What not to do\n\n- Do not hand-roll a third shape. A repo is either Type A or Type B, never both.\n- Do not inline secrets; always use `from_secret`.\n- Do not drop the `check-version` / `check-remote-image` skip — it guards the\n index and the running container against duplicate publishes.\n- Do not rename the pipelines; tooling and runbooks key off\n `build-and-publish-pypi`, `build-and-deploy-service`, and\n `build-and-deploy-site`.\n\n---\n\n## Related\n\n- [01 – Build Recipe](01_build_recipe.md) · [09 – Infra Adapters](09_infra_adaptors.md) · [13 – Library Registry](13_library_registry.md)\n", + "objects": {} + } +} \ No newline at end of file diff --git a/standards/mcp/modules/index.json b/standards/mcp/modules/index.json index 7b4a61e..06d1e45 100644 --- a/standards/mcp/modules/index.json +++ b/standards/mcp/modules/index.json @@ -2,7 +2,7 @@ "module": "index", "content": { "path": "index", - "docstring": "# Standards Reference\n\nThe reference half of the Standards description: the enforceable rules,\npatterns, and the internal library catalog that agents consult while coding.\n\n- **Build Recipe** — the ordered, agentic checklist for a new app.\n- **Layering Rules** — the strict request → domain → repository layering and\n the rules that enforce it.\n- **Composition Root** — a single composition point wiring the whole app.\n- **Spec-First Routing** — the OpenAPI contract as the routing source of truth.\n- **CRUD Template** — the standardized generic resource endpoint scaffold.\n- **Pipeline Scaffold** — the deterministic pipeline skeleton for multi-stage\n jobs.\n- **Worker & Progress** — queued work, progress reporting, and visibility.\n- **Human-in-the-Loop** — guardrails and approvals inside automations.\n- **Infra Adapters** — how infrastructure connects without leaking into domain.\n- **Genericity Matrix** — what is library-backed, template-provided, or authored.\n- **Auth Integration** — consuming the shared auth-server service (spec-driven,\n no auth code).\n- **Library Registry** — the catalog of internal libraries behind all of it.\n\nRead the **Wiki** for the philosophy and rationale; these pages are the\nreference that mirrors the `standards` MCP bundle.\n", + "docstring": "# Standards Reference\n\nThe reference half of the Standards description: the enforceable rules,\npatterns, and the internal library catalog that agents consult while coding.\n\n- **Build Recipe** — the ordered, agentic checklist for a new app.\n- **Layering Rules** — the strict request → domain → repository layering and\n the rules that enforce it.\n- **Composition Root** — a single composition point wiring the whole app.\n- **Spec-First Routing** — the OpenAPI contract as the routing source of truth.\n- **CRUD Template** — the standardized generic resource endpoint scaffold.\n- **Pipeline Scaffold** — the deterministic pipeline skeleton for multi-stage\n jobs.\n- **Worker & Progress** — queued work, progress reporting, and visibility.\n- **Human-in-the-Loop** — guardrails and approvals inside automations.\n- **Infra Adapters** — how infrastructure connects without leaking into domain.\n- **Drone Pipelines** — the two CI shapes: a PyPI package or a Docker deployment.\n- **Genericity Matrix** — what is library-backed, template-provided, or authored.\n- **Auth Integration** — consuming the shared auth-server service (spec-driven,\n no auth code).\n- **Library Registry** — the catalog of internal libraries behind all of it.\n\nRead the **Wiki** for the philosophy and rationale; these pages are the\nreference that mirrors the `standards` MCP bundle.\n", "objects": {} } } \ No newline at end of file diff --git a/standards/mcp/nav.json b/standards/mcp/nav.json index 6d0325b..ceb5833 100644 --- a/standards/mcp/nav.json +++ b/standards/mcp/nav.json @@ -44,6 +44,11 @@ "resource": "docs://modules/infra_adaptors", "title": "Infra Adapters" }, + { + "module": "drone_pipelines", + "resource": "docs://modules/drone_pipelines", + "title": "Drone Pipelines" + }, { "module": "genericity_matrix", "resource": "docs://modules/genericity_matrix", diff --git a/standards/mcp/templates/drone/python-lib.yaml b/standards/mcp/templates/drone/python-lib.yaml new file mode 100644 index 0000000..a0ee7ee --- /dev/null +++ b/standards/mcp/templates/drone/python-lib.yaml @@ -0,0 +1,164 @@ +# Drone standard — Type A: PyPI package +# +# Canonical pipeline for internal Python libraries that ship a wheel/sdist to +# the private index (dagpipe, doc-forge, hexa, mail-intake, mongo-ops, +# omniread, openapi-first, py-jwt). +# +# Two pipelines ship together: +# build-and-publish-pypi — tag-triggered build + quality gate + publish. +# backfill-pypi-from-tags — custom-triggered replay of every git tag that is +# missing from the index (disaster recovery). +# +# Secrets (Drone): PIP_REPO_URL, PIP_USERNAME, PIP_PASSWORD. +# Copy to /.drone.yml unchanged; the package name and version are read +# from pyproject.toml, so no per-repo edits are required. +--- +kind: pipeline +type: docker +name: build-and-publish-pypi + +platform: + os: linux + arch: arm64 + +workspace: + path: /drone/src + +steps: + - name: check-version + image: curlimages/curl:latest + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | head -1 | cut -d'"' -f2) + - VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | cut -d'"' -f2) + - echo "Checking if $PACKAGE_NAME==$VERSION exists on $PIP_REPO_URL ..." + - | + if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" "$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -qE "${PACKAGE_NAME}-${VERSION}(-|\.tar\.gz|\.whl)"; then + echo "$PACKAGE_NAME==$VERSION already exists; skipping build." + exit 78 + else + echo "New version detected: $PACKAGE_NAME==$VERSION" + fi + + - name: quality-gate + image: python:3.13-slim + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - pip install --upgrade pip build + - | + AUTH_URL="https://${PIP_USERNAME}:${PIP_PASSWORD}@$(echo "${PIP_REPO_URL#*://}" | sed 's:/*$::')/simple" + pip install --index-url "$AUTH_URL" --extra-index-url https://pypi.org/simple/ -U ".[dev]" "pydoclint>=0.9.1" + - echo "Running quality gate..." + - python -m black --check . + - python -m ruff check . + - python -m mypy + - pydoclint . + - python -m pytest + + - name: build-package + image: python:3.13-slim + commands: + - pip install --upgrade pip build + - echo "Building Python package..." + - python -m build + - ls -l dist + + - name: upload-to-private-pypi + image: python:3.13-slim + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - pip install --upgrade twine + - echo "Uploading to private PyPI at $PIP_REPO_URL ..." + - | + twine upload \ + --repository-url "$PIP_REPO_URL" \ + -u "$PIP_USERNAME" \ + -p "$PIP_PASSWORD" \ + dist/* + +trigger: + event: + - tag + +--- +kind: pipeline +type: docker +name: backfill-pypi-from-tags + +platform: + os: linux + arch: arm64 + +workspace: + path: /drone/src + +steps: + - name: fetch-tags + image: alpine/git + commands: + - git fetch --tags --force + + - name: build-and-upload-missing + image: python:3.13-slim + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - apt-get update + - apt-get install -y git curl ca-certificates + - pip install --upgrade pip build twine + - | + set -e + + PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | cut -d'"' -f2) + echo "Package: $PACKAGE_NAME" + + for TAG in $(git tag --sort=version:refname); do + VERSION="$TAG" + echo "Version: $VERSION" + + if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" \ + "$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -qE "${PACKAGE_NAME}-${VERSION}(-|\.tar\.gz|\.whl)"; then + echo "Exists, skipping" + continue + fi + + git checkout --force "$TAG" + + echo "Building $VERSION" + rm -rf dist + python -m build + + echo "Uploading $VERSION" + twine upload \ + --repository-url "$PIP_REPO_URL" \ + -u "$PIP_USERNAME" \ + -p "$PIP_PASSWORD" \ + dist/* + done + +trigger: + event: + - custom diff --git a/standards/mcp/templates/drone/service-docker.yaml b/standards/mcp/templates/drone/service-docker.yaml new file mode 100644 index 0000000..4ea17d1 --- /dev/null +++ b/standards/mcp/templates/drone/service-docker.yaml @@ -0,0 +1,257 @@ +# Drone standard — Type B: Docker deployment +# +# Canonical pipelines for services that build an image, push it to the private +# registry, and run it on the Docker host (auth-server, docs, mcp). +# +# Two variants ship together: +# build-and-deploy-service — a long-running API/worker service: fetch the git +# tag, skip if the image already exists, run the +# quality gate, build, push, restart the container. +# build-and-deploy-site — a static/HTTP site: build, push, restart, on +# every push (no tag, no quality gate). +# +# Secrets (Drone): PIP_REPO_URL, PIP_USERNAME, PIP_PASSWORD (service only), +# REGISTRY_HOST, REGISTRY_USER, REGISTRY_PASS, plus the service's runtime env +# (for example MONGO_* / JWT_SECRET). +# +# Placeholders to set per repo: +# image / container / network name (e.g. auth-server). +# aetos/ image repository on the private registry. +# published host port; the in-image port. +# site image name for the site variant (e.g. docs, mcp). +# runtime environment variable consumed by the service. +--- +kind: pipeline +type: docker +name: build-and-deploy-service + +platform: + os: linux + arch: arm64 + +workspace: + path: /drone/src + +volumes: + - name: dockersock + host: + path: /var/run/docker.sock + +steps: + - name: fetch-tags + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - apk add --no-cache git + - git fetch --tags + - | + # Get latest Git tag and trim newline + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null | tr -d '\n') + echo "Latest Git tag fetched: $LATEST_TAG" + + # Save to file for downstream steps + echo "$LATEST_TAG" > /drone/src/LATEST_TAG.txt + + # Read back for verification + IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n') + echo "Image tag read from file: $IMAGE_TAG" + + # Validate + if [ -z "$IMAGE_TAG" ]; then + echo "No git tags found! Cannot continue." + exit 1 + fi + + - name: check-remote-image + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n') + - echo "Checking if aetos/:$IMAGE_TAG exists on remote Docker..." + - echo "Existing Docker tags for aetos/:" + - docker images --format "{{.Repository}}:{{.Tag}}" | grep "^aetos/" || echo "(none)" + - | + if docker image inspect aetos/:$IMAGE_TAG > /dev/null 2>&1; then + echo "Docker image aetos/:$IMAGE_TAG already exists; skipping build" + exit 78 + else + echo "Docker image aetos/:$IMAGE_TAG not found; proceeding to build..." + fi + + - name: quality-gate + image: python:3.13-slim + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - pip install --upgrade pip + - pip install --extra-index-url "https://$PIP_USERNAME:$PIP_PASSWORD@$PIP_REPO_URL/simple" -r requirements.txt + - pip install black ruff mypy + - echo "Running quality gate..." + - python -m black --check . + - python -m ruff check . + - python -m mypy . + - python -m pytest + + - name: build-image + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n') + - echo "Building Docker image aetos/:$IMAGE_TAG ..." + - | + docker build \ + --network=host \ + --build-arg PIP_REPO_URL="$PIP_REPO_URL" \ + --build-arg PIP_USERNAME="$PIP_USERNAME" \ + --build-arg PIP_PASSWORD="$PIP_PASSWORD" \ + -t aetos/:$IMAGE_TAG \ + -t aetos/:latest \ + /drone/src + + - name: push-image + image: docker:24 + environment: + REGISTRY_HOST: + from_secret: REGISTRY_HOST + REGISTRY_USER: + from_secret: REGISTRY_USER + REGISTRY_PASS: + from_secret: REGISTRY_PASS + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n') + - echo "Logging into registry $REGISTRY_HOST ..." + - echo "$REGISTRY_PASS" | docker login $REGISTRY_HOST -u "$REGISTRY_USER" --password-stdin + - echo "Tagging images with registry prefix..." + - docker tag aetos/:$IMAGE_TAG $REGISTRY_HOST/aetos/:$IMAGE_TAG + - docker tag aetos/:$IMAGE_TAG $REGISTRY_HOST/aetos/:latest + - echo "Pushing aetos/:$IMAGE_TAG ..." + - docker push $REGISTRY_HOST/aetos/:$IMAGE_TAG + - echo "Pushing aetos/:latest ..." + - docker push $REGISTRY_HOST/aetos/:latest + + - name: stop-old + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - echo "Stopping old container..." + - docker rm -f || true + + - name: run-container + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + environment: + : + from_secret: + commands: + - IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n') + - echo "Starting container aetos/:$IMAGE_TAG ..." + - | + docker run -d \ + --name \ + -p : \ + --add-host private-pi:192.168.1.111 \ + --restart always \ + -e =$ \ + aetos/:$IMAGE_TAG + +trigger: + event: + - tag + +--- +kind: pipeline +type: docker +name: build-and-deploy-site + +platform: + os: linux + arch: arm64 + +workspace: + path: /drone/src + +volumes: + - name: dockersock + host: + path: /var/run/docker.sock + +steps: + - name: build-image + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - echo "Building Docker image for aetos/..." + - docker build -t aetos/:latest . + + - name: push-image + image: docker:24 + environment: + REGISTRY_HOST: + from_secret: REGISTRY_HOST + REGISTRY_USER: + from_secret: REGISTRY_USER + REGISTRY_PASS: + from_secret: REGISTRY_PASS + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - echo "Logging into registry $REGISTRY_HOST ..." + - echo "$REGISTRY_PASS" | docker login $REGISTRY_HOST -u "$REGISTRY_USER" --password-stdin + - docker tag aetos/:latest $REGISTRY_HOST/aetos/:latest + - echo "Pushing image to $REGISTRY_HOST..." + - docker push $REGISTRY_HOST/aetos/:latest + + - name: stop-old + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - echo "Stopping old container..." + - docker rm -f || true + + - name: run-container + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - echo "Starting aetos/:latest ..." + - | + docker run -d \ + --name \ + -p : \ + --restart always \ + aetos/:latest + +trigger: + event: + - push diff --git a/standards/src/lib/10_drone_pipelines.md b/standards/src/lib/10_drone_pipelines.md new file mode 100644 index 0000000..ca0e4aa --- /dev/null +++ b/standards/src/lib/10_drone_pipelines.md @@ -0,0 +1,102 @@ +# Drone Pipelines + +## Summary + +Every Aetos repository ships a Drone CI definition at `.drone.yml`. There is +exactly **one standard per repository**, and there are **two types of file**, +chosen by what the repository produces: + +| Type | File | Produces | Repos | +|------|------|----------|-------| +| **A — PyPI package** | `templates/drone/python-lib.yaml` | A wheel + sdist on the private index | `dagpipe`, `doc-forge`, `hexa`, `mail-intake`, `mongo-ops`, `omniread`, `openapi-first`, `py-jwt` | +| **B — Docker deployment** | `templates/drone/service-docker.yaml` | An image pushed to the registry and run as a container | `auth-server`, `docs`, `mcp` | + +Both templates run on `platform: linux / arm64` with the workspace at +`/drone/src`. Copy the matching template to `/.drone.yml`; the Type A file +needs no edits, the Type B file needs its placeholders filled in (below). + +> Conventions: `.drone.yml` (not `.yaml`), one file per repo, secrets injected +> with `from_secret` — never commit credentials. + +--- + +## Type A — PyPI package (`python-lib.yaml`) + +For internal libraries. The file contains **two pipelines**: + +1. **`build-and-publish-pypi`** — triggered on `tag`: + - `check-version` — read `name` / `version` from `pyproject.toml` and query + `$PIP_REPO_URL/simple//`; if that version already exists, exit + `78` and skip the whole build. + - `quality-gate` — install the package with its `[dev]` extras from the + private index, then `black --check`, `ruff check`, `mypy`, `pydoclint`, + `pytest`. + - `build-package` — `python -m build` into `dist/`. + - `upload-to-private-pypi` — `twine upload` to `$PIP_REPO_URL`. +2. **`backfill-pypi-from-tags`** — triggered on `custom`: + - `fetch-tags` — `git fetch --tags --force`. + - `build-and-upload-missing` — walk every tag in version order, skip the ones + already on the index, `git checkout` the rest, build, and upload. This is + the disaster-recovery / replay path. + +Secrets: `PIP_REPO_URL`, `PIP_USERNAME`, `PIP_PASSWORD`. + +- **Ship as-is.** Package name and version come from `pyproject.toml`, so the + same file is reused verbatim across every library. + +--- + +## Type B — Docker deployment (`service-docker.yaml`) + +For services and sites that ship a container image. The file contains **two +pipelines**, each mirroring a repo family: + +1. **`build-and-deploy-service`** — triggered on `tag` (a long-running + API/worker service, e.g. `auth-server`): + - `fetch-tags` — resolve the latest tag into `LATEST_TAG.txt`; fail if there + are no tags. + - `check-remote-image` — if `aetos/:` already exists locally, exit + `78` and skip the build. + - `quality-gate` — install from `requirements.txt` and run + `black` / `ruff` / `mypy` / `pytest`. + - `build-image` — `docker build` with the private-index build args, tagged + `aetos/:` and `:latest`. + - `push-image` — login, re-tag with `$REGISTRY_HOST`, push both tags. + - `stop-old` — `docker rm -f `. + - `run-container` — `docker run -d --name -p : + --restart always` with the service's `-e` environment. +2. **`build-and-deploy-site`** — triggered on `push` (a static/HTTP site, e.g. + `docs`, `mcp`): `build-image` → `push-image` → `stop-old` → `run-container`, + with no tag resolution and no quality gate. + +Secrets: `PIP_REPO_URL`, `PIP_USERNAME`, `PIP_PASSWORD` (service build args), +`REGISTRY_HOST`, `REGISTRY_USER`, `REGISTRY_PASS`, plus the service runtime env +(for example `MONGO_*`, `JWT_SECRET`). + +Placeholders to fill in per repo: + +| Placeholder | Meaning | Example | +|-------------|---------|---------| +| `` | service image / container name | `auth-server` | +| `aetos/` | image repository on the registry | `aetos/auth-server` | +| `` / `` | published : in-image port | `9003:8000` | +| `` | site image / container name | `docs` | +| `` | runtime environment variable | `MONGO_HOST` | + +--- + +## What not to do + +- Do not hand-roll a third shape. A repo is either Type A or Type B, never both. +- Do not inline secrets; always use `from_secret`. +- Do not drop the `check-version` / `check-remote-image` skip — it guards the + index and the running container against duplicate publishes. +- Do not rename the pipelines; tooling and runbooks key off + `build-and-publish-pypi`, `build-and-deploy-service`, and + `build-and-deploy-site`. + +--- + +## Related + +- [01 – Build Recipe](01_build_recipe.md) · [09 – Infra Adapters](09_infra_adaptors.md) · [13 – Library Registry](13_library_registry.md) diff --git a/standards/src/lib/index.md b/standards/src/lib/index.md index 9f36f6e..d8f1141 100644 --- a/standards/src/lib/index.md +++ b/standards/src/lib/index.md @@ -14,6 +14,7 @@ patterns, and the internal library catalog that agents consult while coding. - **Worker & Progress** — queued work, progress reporting, and visibility. - **Human-in-the-Loop** — guardrails and approvals inside automations. - **Infra Adapters** — how infrastructure connects without leaking into domain. +- **Drone Pipelines** — the two CI shapes: a PyPI package or a Docker deployment. - **Genericity Matrix** — what is library-backed, template-provided, or authored. - **Auth Integration** — consuming the shared auth-server service (spec-driven, no auth code). diff --git a/standards/src/mkdocs.lib.yml b/standards/src/mkdocs.lib.yml index db147e4..a99036d 100644 --- a/standards/src/mkdocs.lib.yml +++ b/standards/src/mkdocs.lib.yml @@ -63,6 +63,7 @@ nav: - Worker & Progress: 07_worker_and_progress.md - Human-in-the-Loop: 08_human_in_loop.md - Infra Adapters: 09_infra_adaptors.md + - Drone Pipelines: 10_drone_pipelines.md - Genericity Matrix: 12_genericity_matrix.md - Auth Integration: 14_auth_integration.md - Library Registry: 13_library_registry.md \ No newline at end of file diff --git a/standards/src/templates/drone/python-lib.yaml b/standards/src/templates/drone/python-lib.yaml new file mode 100644 index 0000000..a0ee7ee --- /dev/null +++ b/standards/src/templates/drone/python-lib.yaml @@ -0,0 +1,164 @@ +# Drone standard — Type A: PyPI package +# +# Canonical pipeline for internal Python libraries that ship a wheel/sdist to +# the private index (dagpipe, doc-forge, hexa, mail-intake, mongo-ops, +# omniread, openapi-first, py-jwt). +# +# Two pipelines ship together: +# build-and-publish-pypi — tag-triggered build + quality gate + publish. +# backfill-pypi-from-tags — custom-triggered replay of every git tag that is +# missing from the index (disaster recovery). +# +# Secrets (Drone): PIP_REPO_URL, PIP_USERNAME, PIP_PASSWORD. +# Copy to /.drone.yml unchanged; the package name and version are read +# from pyproject.toml, so no per-repo edits are required. +--- +kind: pipeline +type: docker +name: build-and-publish-pypi + +platform: + os: linux + arch: arm64 + +workspace: + path: /drone/src + +steps: + - name: check-version + image: curlimages/curl:latest + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | head -1 | cut -d'"' -f2) + - VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | cut -d'"' -f2) + - echo "Checking if $PACKAGE_NAME==$VERSION exists on $PIP_REPO_URL ..." + - | + if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" "$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -qE "${PACKAGE_NAME}-${VERSION}(-|\.tar\.gz|\.whl)"; then + echo "$PACKAGE_NAME==$VERSION already exists; skipping build." + exit 78 + else + echo "New version detected: $PACKAGE_NAME==$VERSION" + fi + + - name: quality-gate + image: python:3.13-slim + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - pip install --upgrade pip build + - | + AUTH_URL="https://${PIP_USERNAME}:${PIP_PASSWORD}@$(echo "${PIP_REPO_URL#*://}" | sed 's:/*$::')/simple" + pip install --index-url "$AUTH_URL" --extra-index-url https://pypi.org/simple/ -U ".[dev]" "pydoclint>=0.9.1" + - echo "Running quality gate..." + - python -m black --check . + - python -m ruff check . + - python -m mypy + - pydoclint . + - python -m pytest + + - name: build-package + image: python:3.13-slim + commands: + - pip install --upgrade pip build + - echo "Building Python package..." + - python -m build + - ls -l dist + + - name: upload-to-private-pypi + image: python:3.13-slim + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - pip install --upgrade twine + - echo "Uploading to private PyPI at $PIP_REPO_URL ..." + - | + twine upload \ + --repository-url "$PIP_REPO_URL" \ + -u "$PIP_USERNAME" \ + -p "$PIP_PASSWORD" \ + dist/* + +trigger: + event: + - tag + +--- +kind: pipeline +type: docker +name: backfill-pypi-from-tags + +platform: + os: linux + arch: arm64 + +workspace: + path: /drone/src + +steps: + - name: fetch-tags + image: alpine/git + commands: + - git fetch --tags --force + + - name: build-and-upload-missing + image: python:3.13-slim + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - apt-get update + - apt-get install -y git curl ca-certificates + - pip install --upgrade pip build twine + - | + set -e + + PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | cut -d'"' -f2) + echo "Package: $PACKAGE_NAME" + + for TAG in $(git tag --sort=version:refname); do + VERSION="$TAG" + echo "Version: $VERSION" + + if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" \ + "$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -qE "${PACKAGE_NAME}-${VERSION}(-|\.tar\.gz|\.whl)"; then + echo "Exists, skipping" + continue + fi + + git checkout --force "$TAG" + + echo "Building $VERSION" + rm -rf dist + python -m build + + echo "Uploading $VERSION" + twine upload \ + --repository-url "$PIP_REPO_URL" \ + -u "$PIP_USERNAME" \ + -p "$PIP_PASSWORD" \ + dist/* + done + +trigger: + event: + - custom diff --git a/standards/src/templates/drone/service-docker.yaml b/standards/src/templates/drone/service-docker.yaml new file mode 100644 index 0000000..4ea17d1 --- /dev/null +++ b/standards/src/templates/drone/service-docker.yaml @@ -0,0 +1,257 @@ +# Drone standard — Type B: Docker deployment +# +# Canonical pipelines for services that build an image, push it to the private +# registry, and run it on the Docker host (auth-server, docs, mcp). +# +# Two variants ship together: +# build-and-deploy-service — a long-running API/worker service: fetch the git +# tag, skip if the image already exists, run the +# quality gate, build, push, restart the container. +# build-and-deploy-site — a static/HTTP site: build, push, restart, on +# every push (no tag, no quality gate). +# +# Secrets (Drone): PIP_REPO_URL, PIP_USERNAME, PIP_PASSWORD (service only), +# REGISTRY_HOST, REGISTRY_USER, REGISTRY_PASS, plus the service's runtime env +# (for example MONGO_* / JWT_SECRET). +# +# Placeholders to set per repo: +# image / container / network name (e.g. auth-server). +# aetos/ image repository on the private registry. +# published host port; the in-image port. +# site image name for the site variant (e.g. docs, mcp). +# runtime environment variable consumed by the service. +--- +kind: pipeline +type: docker +name: build-and-deploy-service + +platform: + os: linux + arch: arm64 + +workspace: + path: /drone/src + +volumes: + - name: dockersock + host: + path: /var/run/docker.sock + +steps: + - name: fetch-tags + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - apk add --no-cache git + - git fetch --tags + - | + # Get latest Git tag and trim newline + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null | tr -d '\n') + echo "Latest Git tag fetched: $LATEST_TAG" + + # Save to file for downstream steps + echo "$LATEST_TAG" > /drone/src/LATEST_TAG.txt + + # Read back for verification + IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n') + echo "Image tag read from file: $IMAGE_TAG" + + # Validate + if [ -z "$IMAGE_TAG" ]; then + echo "No git tags found! Cannot continue." + exit 1 + fi + + - name: check-remote-image + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n') + - echo "Checking if aetos/:$IMAGE_TAG exists on remote Docker..." + - echo "Existing Docker tags for aetos/:" + - docker images --format "{{.Repository}}:{{.Tag}}" | grep "^aetos/" || echo "(none)" + - | + if docker image inspect aetos/:$IMAGE_TAG > /dev/null 2>&1; then + echo "Docker image aetos/:$IMAGE_TAG already exists; skipping build" + exit 78 + else + echo "Docker image aetos/:$IMAGE_TAG not found; proceeding to build..." + fi + + - name: quality-gate + image: python:3.13-slim + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - pip install --upgrade pip + - pip install --extra-index-url "https://$PIP_USERNAME:$PIP_PASSWORD@$PIP_REPO_URL/simple" -r requirements.txt + - pip install black ruff mypy + - echo "Running quality gate..." + - python -m black --check . + - python -m ruff check . + - python -m mypy . + - python -m pytest + + - name: build-image + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + environment: + PIP_REPO_URL: + from_secret: PIP_REPO_URL + PIP_USERNAME: + from_secret: PIP_USERNAME + PIP_PASSWORD: + from_secret: PIP_PASSWORD + commands: + - IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n') + - echo "Building Docker image aetos/:$IMAGE_TAG ..." + - | + docker build \ + --network=host \ + --build-arg PIP_REPO_URL="$PIP_REPO_URL" \ + --build-arg PIP_USERNAME="$PIP_USERNAME" \ + --build-arg PIP_PASSWORD="$PIP_PASSWORD" \ + -t aetos/:$IMAGE_TAG \ + -t aetos/:latest \ + /drone/src + + - name: push-image + image: docker:24 + environment: + REGISTRY_HOST: + from_secret: REGISTRY_HOST + REGISTRY_USER: + from_secret: REGISTRY_USER + REGISTRY_PASS: + from_secret: REGISTRY_PASS + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n') + - echo "Logging into registry $REGISTRY_HOST ..." + - echo "$REGISTRY_PASS" | docker login $REGISTRY_HOST -u "$REGISTRY_USER" --password-stdin + - echo "Tagging images with registry prefix..." + - docker tag aetos/:$IMAGE_TAG $REGISTRY_HOST/aetos/:$IMAGE_TAG + - docker tag aetos/:$IMAGE_TAG $REGISTRY_HOST/aetos/:latest + - echo "Pushing aetos/:$IMAGE_TAG ..." + - docker push $REGISTRY_HOST/aetos/:$IMAGE_TAG + - echo "Pushing aetos/:latest ..." + - docker push $REGISTRY_HOST/aetos/:latest + + - name: stop-old + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - echo "Stopping old container..." + - docker rm -f || true + + - name: run-container + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + environment: + : + from_secret: + commands: + - IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n') + - echo "Starting container aetos/:$IMAGE_TAG ..." + - | + docker run -d \ + --name \ + -p : \ + --add-host private-pi:192.168.1.111 \ + --restart always \ + -e =$ \ + aetos/:$IMAGE_TAG + +trigger: + event: + - tag + +--- +kind: pipeline +type: docker +name: build-and-deploy-site + +platform: + os: linux + arch: arm64 + +workspace: + path: /drone/src + +volumes: + - name: dockersock + host: + path: /var/run/docker.sock + +steps: + - name: build-image + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - echo "Building Docker image for aetos/..." + - docker build -t aetos/:latest . + + - name: push-image + image: docker:24 + environment: + REGISTRY_HOST: + from_secret: REGISTRY_HOST + REGISTRY_USER: + from_secret: REGISTRY_USER + REGISTRY_PASS: + from_secret: REGISTRY_PASS + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - echo "Logging into registry $REGISTRY_HOST ..." + - echo "$REGISTRY_PASS" | docker login $REGISTRY_HOST -u "$REGISTRY_USER" --password-stdin + - docker tag aetos/:latest $REGISTRY_HOST/aetos/:latest + - echo "Pushing image to $REGISTRY_HOST..." + - docker push $REGISTRY_HOST/aetos/:latest + + - name: stop-old + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - echo "Stopping old container..." + - docker rm -f || true + + - name: run-container + image: docker:24 + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - echo "Starting aetos/:latest ..." + - | + docker run -d \ + --name \ + -p : \ + --restart always \ + aetos/:latest + +trigger: + event: + - push