1
0
mirror of https://github.com/sredevopsorg/deepseek-kubernetes-sandbox.git synced 2026-11-03 01:13:24 +00:00

feat(repo)!: restructure around the dsh-harness image and one example per implementation

- the root Dockerfile is the primary artifact; versions.env is the single source of truth
- merge gateway-api/dsh-demo into examples/dsh-agents-as-a-service (manifests/, exposure variants)
- move the single-tenant template into examples/minimal-single-tenant
- publish the gateway as a nested package under the repository image
- mirror the DSH ref for release identities (first release dsh-v0.2.1-rc.2-r2)
- CI: lint/python/manifests/gateway-image, reusable multi-arch build, tag-driven release, weekly e2e
- docs: README, docs/{main-image,examples,gitflow,versioning}, example READMEs, AGENTS.md, governance
- SSO is opt-in again; gateway pull policy and cookie defaults match the documented demo

BREAKING CHANGE: example manifest paths, the gateway image name, and the removal
of deploy/ and gateway-api/ affect existing deployments; see the migration table
in README.md.
This commit is contained in:
SREDevOpsAgents
2026-10-25 06:37:28 -03:00
parent 53e55fc851
commit e6e109d9a9
68 changed files with 3713 additions and 1643 deletions

56
scripts/check-examples.sh Executable file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Enforce the example contract documented in docs/examples.md.
# Run by "make check" locally and by the CI lint job.
set -euo pipefail
cd "$(dirname "$0")/.."
index=examples/README.md
fail=0
err() { echo "check-examples: $*" >&2; fail=1; }
[ -f "$index" ] || err "missing $index"
for dir in examples/*/; do
name="$(basename "$dir")"
[ "$name" = "_template" ] && continue
[ -f "${dir}README.md" ] || err "$name: missing README.md"
[ -d "${dir}manifests" ] || err "$name: missing manifests/"
if [ -d "${dir}manifests" ]; then
count=$(find "${dir}manifests" -maxdepth 1 -type f -name '*.yaml' | wc -l | tr -d ' ')
[ "$count" -gt 0 ] || err "$name: manifests/ has no top-level *.yaml files"
fi
stray=$(find "$dir" -maxdepth 1 -type f -name '*.yaml' | wc -l | tr -d ' ')
[ "$stray" -eq 0 ] || err "$name: manifests belong under manifests/, not at the example root"
if [ -d "${dir}manifests/exposure" ] && [ ! -f "${dir}manifests/exposure/README.md" ]; then
err "$name: manifests/exposure/ requires a README.md describing the variants"
fi
for component in "$dir"*/; do
[ -d "$component" ] || continue
case "$(basename "$component")" in
manifests|scripts|docs) continue ;;
esac
if [ -f "${component}Dockerfile" ] && [ ! -f "${component}README.md" ]; then
err "$name/$(basename "$component"): component with a Dockerfile needs a README.md"
fi
done
if ! grep -Fq "examples/$name/" "$index"; then
err "$name: not listed in examples/README.md"
fi
for script in "$dir"scripts/*.sh; do
[ -e "$script" ] || continue
[ -x "$script" ] || err "$script: must be executable"
done
done
if [ "$fail" -ne 0 ]; then
echo "check-examples: FAILED" >&2
exit 1
fi
echo "check-examples: ok"

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""Parse every example manifest and assert the minimal Kubernetes shape."""
from __future__ import annotations
import sys
from pathlib import Path
try:
import yaml
except ModuleNotFoundError: # pragma: no cover - environment guard
sys.exit("check-manifests: PyYAML is required (python -m pip install pyyaml)")
ROOT = Path(__file__).resolve().parent.parent
FILES = sorted(ROOT.glob("examples/*/manifests/**/*.yaml"))
if not FILES:
sys.exit("check-manifests: no manifests found under examples/*/manifests/")
errors: list[str] = []
documents = 0
for path in FILES:
relative = path.relative_to(ROOT)
try:
loaded = list(yaml.safe_load_all(path.read_text(encoding="utf-8")))
except yaml.YAMLError as exc:
errors.append(f"{relative}: invalid YAML: {exc}")
continue
for position, document in enumerate(loaded, start=1):
if document is None:
continue
documents += 1
if not isinstance(document, dict):
errors.append(f"{relative} document {position}: not a mapping")
continue
missing = [key for key in ("apiVersion", "kind", "metadata") if key not in document]
if missing:
errors.append(f"{relative} document {position}: missing {', '.join(missing)}")
continue
metadata = document["metadata"]
if not isinstance(metadata, dict) or not metadata.get("name"):
errors.append(f"{relative} document {position}: metadata.name is required")
if errors:
print("\n".join(errors), file=sys.stderr)
sys.exit(1)
print(f"check-manifests: ok ({len(FILES)} files, {documents} documents)")

70
scripts/check-versions.sh Executable file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Verify that every version pin in the repository agrees with versions.env.
# Run by "make check" locally and by the CI lint job.
set -euo pipefail
cd "$(dirname "$0")/.."
# shellcheck source=/dev/null
. ./versions.env
IDENTITY="$DSH_VERSION"
if [ -n "${RELEASE_REVISION:-}" ]; then
IDENTITY="$DSH_VERSION-$RELEASE_REVISION"
fi
fail=0
err() { echo "check-versions: $*" >&2; fail=1; }
expect() { # <file> <literal>
if ! grep -Fq -- "$2" "$1"; then
err "$1: expected to contain: $2"
fi
}
# Main Dockerfile build args
expect Dockerfile "ARG DSH_VERSION=$DSH_VERSION"
expect Dockerfile "ARG PNPM_VERSION=$PNPM_VERSION"
expect Dockerfile "ARG RELEASE_REVISION=$RELEASE_REVISION"
# Local configuration mirrored for humans
expect .env.sample "DSH_VERSION=$DSH_VERSION"
expect .env.sample "PNPM_VERSION=$PNPM_VERSION"
expect .env.sample "RELEASE_REVISION=$RELEASE_REVISION"
# Image references in the example manifests
for f in \
examples/dsh-agents-as-a-service/manifests/10-sandboxtemplate.yaml \
examples/minimal-single-tenant/manifests/sandbox-template.yaml; do
expect "$f" "image: $GHCR_IMAGE:$IDENTITY"
done
expect examples/dsh-agents-as-a-service/manifests/50-gateway.yaml "image: $GATEWAY_IMAGE:$IDENTITY"
# The gateway image must live below the repository image (one package per
# example component, no unrelated top-level packages).
case "$GATEWAY_IMAGE" in
"$GHCR_IMAGE"/*) ;;
*) err "GATEWAY_IMAGE ($GATEWAY_IMAGE) must be nested below GHCR_IMAGE ($GHCR_IMAGE)" ;;
esac
# Build tooling must read versions.env instead of hard-coding a version.
# (scripts/ is excluded: set-version.sh documents usage examples in comments.)
while IFS= read -r hit; do
[ -n "$hit" ] || continue
err "hard-coded version outside versions.env: $hit"
done < <(grep -rnE 'dsh-v[0-9]' .github/workflows Makefile 2>/dev/null || true)
# Prose may mention versions, but only the current identity or its base tag.
while IFS= read -r token; do
[ -n "$token" ] || continue
case "$token" in
"$IDENTITY"|"$DSH_VERSION") ;;
*) err "stale version reference in docs: $token" ;;
esac
done < <(grep -rhoE 'dsh-v[0-9][A-Za-z0-9._-]*' \
--include='*.md' --exclude-dir=.git --exclude-dir=.venv --exclude-dir=.uvcache \
--exclude=CHANGELOG.md . | sort -u)
if [ "$fail" -ne 0 ]; then
echo "check-versions: FAILED" >&2
exit 1
fi
echo "check-versions: ok ($IDENTITY)"

57
scripts/set-version.sh Executable file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Rewrite every version pin in one pass, then leave the tree ready for
# "make check".
#
# ./scripts/set-version.sh dsh-v0.2.2-rc.1 # new DSH ref, no revision
# ./scripts/set-version.sh dsh-v0.2.1-rc.2 r3 # same ref, new repo revision
set -euo pipefail
cd "$(dirname "$0")/.."
new_version="${1:-}"
new_revision="${2:-}"
case "$new_version" in
dsh-v[0-9]*) ;;
*) echo "usage: $0 <dsh-vX.Y.Z> [rN]" >&2; exit 2 ;;
esac
if [ -n "$new_revision" ]; then
case "$new_revision" in
r[2-9]|r[1-9][0-9]*) ;;
*) echo "revision must look like r2, r3, ..." >&2; exit 2 ;;
esac
fi
# shellcheck source=/dev/null
old_version="$(. ./versions.env; printf '%s' "$DSH_VERSION")"
# shellcheck source=/dev/null
old_revision="$(. ./versions.env; printf '%s' "${RELEASE_REVISION:-}")"
old_identity="$old_version"
[ -z "$old_revision" ] || old_identity="$old_version-$old_revision"
new_identity="$new_version"
[ -z "$new_revision" ] || new_identity="$new_version-$new_revision"
if [ "$old_identity" = "$new_identity" ]; then
echo "set-version: already at $new_identity"
exit 0
fi
# Replace the identity first (it contains the base version), then the base
# version itself. CHANGELOG.md is generated from tags, so it is left alone.
while IFS= read -r file; do
[ -f "$file" ] || continue
case "$file" in
.env.sample|Dockerfile|Makefile|*.md|*.yaml|*.yml|*.sh|*.py|*.toml|*.env) ;;
*) continue ;;
esac
sed -i "s/$old_identity/$new_identity/g; s/$old_version/$new_version/g" "$file"
done < <(git ls-files)
# versions.env is authoritative: write both keys explicitly so an empty
# revision is stored as an empty value.
sed -i "s/^DSH_VERSION=.*/DSH_VERSION=$new_version/" versions.env
sed -i "s/^RELEASE_REVISION=.*/RELEASE_REVISION=$new_revision/" versions.env
echo "set-version: $old_identity -> $new_identity"
echo "set-version: run 'make check' and review the diff"