Wire push deployment for all services
All checks were successful
deploy / deploy (push) Successful in 30s
All checks were successful
deploy / deploy (push) Successful in 30s
Proof: Push-driven repo workflow now renders and applies the built image across all repo-owned deployments instead of resetting services to placeholder images or relying on a manual rollout list. Assumptions: All repo-owned workloads that should roll on push carry app.kubernetes.io/part-of= in the manifests, and namespace bootstrap can happen before the image build without applying placeholder deployments. Still fake: This turn fixes the repo deployment path in code, but I have not yet exercised the new Forgejo workflow end-to-end from a fresh push on the cluster.
This commit is contained in:
parent
3c1ad1dde4
commit
deda0002ab
5 changed files with 92 additions and 15 deletions
|
|
@ -14,30 +14,29 @@ jobs:
|
||||||
REGISTRY_HOST: ${{ vars.REGISTRY_HOST }}
|
REGISTRY_HOST: ${{ vars.REGISTRY_HOST }}
|
||||||
PROJECT_NAME: ${{ vars.PROJECT_NAME || 'unrip' }}
|
PROJECT_NAME: ${{ vars.PROJECT_NAME || 'unrip' }}
|
||||||
PROJECT_NAMESPACE: ${{ vars.PROJECT_NAMESPACE || vars.PROJECT_NAME || 'unrip' }}
|
PROJECT_NAMESPACE: ${{ vars.PROJECT_NAMESPACE || vars.PROJECT_NAME || 'unrip' }}
|
||||||
PROJECT_DEPLOYMENTS: ${{ vars.PROJECT_DEPLOYMENTS || 'near-intents-ingest,market-reference-ingest,liquidity-manager,inventory-sync,history-writer,strategy-engine,trade-executor' }}
|
|
||||||
PROJECT_REGISTRY_SECRET_NAME: ${{ vars.PROJECT_REGISTRY_SECRET_NAME || format('{0}-registry-creds', vars.PROJECT_NAME || 'unrip') }}
|
PROJECT_REGISTRY_SECRET_NAME: ${{ vars.PROJECT_REGISTRY_SECRET_NAME || format('{0}-registry-creds', vars.PROJECT_NAME || 'unrip') }}
|
||||||
REPO_CLONE_URL: ${{ github.server_url }}/${{ github.repository }}.git
|
REPO_CLONE_URL: ${{ github.server_url }}/${{ github.repository }}.git
|
||||||
steps:
|
steps:
|
||||||
- name: Install tooling
|
- name: Install tooling
|
||||||
run: |
|
run: |
|
||||||
if command -v git >/dev/null 2>&1 && command -v kubectl >/dev/null 2>&1; then
|
if command -v git >/dev/null 2>&1 && command -v kubectl >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command -v apk >/dev/null 2>&1; then
|
if command -v apk >/dev/null 2>&1; then
|
||||||
apk add --no-cache git kubectl
|
apk add --no-cache git kubectl python3
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y git curl ca-certificates
|
apt-get install -y git curl ca-certificates python3
|
||||||
curl -fsSLo /usr/local/bin/kubectl "https://dl.k8s.io/release/$(curl -fsSL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
|
curl -fsSLo /usr/local/bin/kubectl "https://dl.k8s.io/release/$(curl -fsSL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
|
||||||
chmod +x /usr/local/bin/kubectl
|
chmod +x /usr/local/bin/kubectl
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "missing git/kubectl and no supported package manager found" >&2
|
echo "missing git/kubectl/python3 and no supported package manager found" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Prepare workspace
|
- name: Prepare workspace
|
||||||
|
|
@ -77,9 +76,9 @@ jobs:
|
||||||
echo "BUILD_JOB=$BUILD_JOB"
|
echo "BUILD_JOB=$BUILD_JOB"
|
||||||
} >> "$GITHUB_ENV"
|
} >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Apply manifests
|
- name: Ensure namespace exists
|
||||||
run: |
|
run: |
|
||||||
kubectl apply -k "$WORKSPACE_DIR/deploy/k8s/base"
|
kubectl apply -f "$WORKSPACE_DIR/deploy/k8s/base/namespace.yaml"
|
||||||
|
|
||||||
- name: Build and push image in-cluster
|
- name: Build and push image in-cluster
|
||||||
env:
|
env:
|
||||||
|
|
@ -144,12 +143,16 @@ jobs:
|
||||||
kubectl -n "$PROJECT_NAMESPACE" wait --for=condition=Complete --timeout=20m "job/$BUILD_JOB"
|
kubectl -n "$PROJECT_NAMESPACE" wait --for=condition=Complete --timeout=20m "job/$BUILD_JOB"
|
||||||
kubectl -n "$PROJECT_NAMESPACE" logs "job/$BUILD_JOB"
|
kubectl -n "$PROJECT_NAMESPACE" logs "job/$BUILD_JOB"
|
||||||
|
|
||||||
- name: Roll deployments to new image
|
- name: Apply release manifests and wait for rollout
|
||||||
run: |
|
run: |
|
||||||
printf '%s\n' "$PROJECT_DEPLOYMENTS" | tr ',' '\n' | while IFS= read -r deployment; do
|
kubectl kustomize "$WORKSPACE_DIR/deploy/k8s/base" \
|
||||||
deployment="$(echo "$deployment" | xargs)"
|
| python3 "$WORKSPACE_DIR/scripts/deploy/render_release_manifest.py" --image "$IMAGE" \
|
||||||
[ -n "$deployment" ] || continue
|
| kubectl apply -f -
|
||||||
|
|
||||||
kubectl -n "$PROJECT_NAMESPACE" set image "deployment/$deployment" app="$IMAGE"
|
kubectl -n "$PROJECT_NAMESPACE" get deployment \
|
||||||
kubectl -n "$PROJECT_NAMESPACE" rollout status "deployment/$deployment" --timeout=180s
|
-l "app.kubernetes.io/part-of=$PROJECT_NAME" \
|
||||||
done
|
-o name \
|
||||||
|
| while IFS= read -r deployment; do
|
||||||
|
[ -n "$deployment" ] || continue
|
||||||
|
kubectl -n "$PROJECT_NAMESPACE" rollout status "$deployment" --timeout=180s
|
||||||
|
done
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,9 @@ Read:
|
||||||
- Do not expand scope beyond the active implementation proof or research charter.
|
- Do not expand scope beyond the active implementation proof or research charter.
|
||||||
- No backlog generation instead of implementation.
|
- No backlog generation instead of implementation.
|
||||||
- No scaffolding ahead of demonstrated need.
|
- No scaffolding ahead of demonstrated need.
|
||||||
|
- The repository must be fully wired so every service deploys automatically from a repo push.
|
||||||
|
- Manual deployment intervention is forbidden.
|
||||||
|
- Manual `kubectl` rollout, image patching, or other ad hoc production reconciliation is forbidden except when the user explicitly asks for emergency break-glass incident handling.
|
||||||
- Quote collection and analytics are first-class from day one. They are not a later add-on.
|
- Quote collection and analytics are first-class from day one. They are not a later add-on.
|
||||||
- Do not present scaffolding, dashboards, placeholders, or mock flows as product progress.
|
- Do not present scaffolding, dashboards, placeholders, or mock flows as product progress.
|
||||||
- State assumptions before coding when the environment, venue, chain, or source behavior is uncertain.
|
- State assumptions before coding when the environment, venue, chain, or source behavior is uncertain.
|
||||||
|
|
@ -129,6 +132,7 @@ Real progress means the repository can do more of the active proof with validate
|
||||||
- storing durable inventory, pricing, decision, and execution records
|
- storing durable inventory, pricing, decision, and execution records
|
||||||
- producing a real decision from live data
|
- producing a real decision from live data
|
||||||
- making a real execution attempt through repo-controlled code
|
- making a real execution attempt through repo-controlled code
|
||||||
|
- deploying all repo-owned services automatically from push-driven repo workflow without manual cluster intervention
|
||||||
- proving blocked-path safety with explicit evidence
|
- proving blocked-path safety with explicit evidence
|
||||||
|
|
||||||
Fake progress includes:
|
Fake progress includes:
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ FORGEJO_REMOTE_NAME="${FORGEJO_REMOTE_NAME:-forgejo}"
|
||||||
|
|
||||||
PROJECT_NAME="${PROJECT_NAME:-unrip}"
|
PROJECT_NAME="${PROJECT_NAME:-unrip}"
|
||||||
PROJECT_NAMESPACE="${PROJECT_NAMESPACE:-$PROJECT_NAME}"
|
PROJECT_NAMESPACE="${PROJECT_NAMESPACE:-$PROJECT_NAME}"
|
||||||
PROJECT_DEPLOYMENTS="${PROJECT_DEPLOYMENTS:-near-intents-ingest,market-reference-ingest,liquidity-manager,inventory-sync,history-writer,ops-sentinel,strategy-engine,trade-executor}"
|
PROJECT_DEPLOYMENTS="${PROJECT_DEPLOYMENTS:-near-intents-ingest,market-reference-ingest,liquidity-manager,inventory-sync,history-writer,ops-sentinel,strategy-engine,trade-executor,operator-dashboard}"
|
||||||
PROJECT_REGISTRY_SECRET_NAME="${PROJECT_REGISTRY_SECRET_NAME:-${PROJECT_NAME}-registry-creds}"
|
PROJECT_REGISTRY_SECRET_NAME="${PROJECT_REGISTRY_SECRET_NAME:-${PROJECT_NAME}-registry-creds}"
|
||||||
APP_SECRET_NAME="${APP_SECRET_NAME:-${PROJECT_NAME}-secrets}"
|
APP_SECRET_NAME="${APP_SECRET_NAME:-${PROJECT_NAME}-secrets}"
|
||||||
SYNC_FORGEJO_REMOTE="${SYNC_FORGEJO_REMOTE:-1}"
|
SYNC_FORGEJO_REMOTE="${SYNC_FORGEJO_REMOTE:-1}"
|
||||||
|
|
|
||||||
28
scripts/deploy/render_release_manifest.py
Normal file
28
scripts/deploy/render_release_manifest.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
PLACEHOLDER_IMAGE = "ghcr.io/example/unrip:bootstrap"
|
||||||
|
|
||||||
|
|
||||||
|
def render_release_manifest(manifest: str, image: str) -> str:
|
||||||
|
return manifest.replace(PLACEHOLDER_IMAGE, image)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Render a release manifest by replacing placeholder app images."
|
||||||
|
)
|
||||||
|
parser.add_argument("--image", required=True, help="Fully qualified image reference to deploy.")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
source = sys.stdin.read()
|
||||||
|
sys.stdout.write(render_release_manifest(source, args.image))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
42
test/render_release_manifest_test.py
Normal file
42
test/render_release_manifest_test.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from scripts.deploy.render_release_manifest import render_release_manifest
|
||||||
|
|
||||||
|
|
||||||
|
class RenderReleaseManifestTest(unittest.TestCase):
|
||||||
|
def test_swaps_placeholder_images_for_release_image(self):
|
||||||
|
input_manifest = """
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: operator-dashboard
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
image: ghcr.io/example/unrip:bootstrap
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: ops-sentinel
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
image: ghcr.io/example/unrip:bootstrap
|
||||||
|
"""
|
||||||
|
|
||||||
|
output = render_release_manifest(
|
||||||
|
input_manifest,
|
||||||
|
"registry.example/unrip:abc123",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn("image: registry.example/unrip:abc123", output)
|
||||||
|
self.assertNotIn("ghcr.io/example/unrip:bootstrap", output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Add table
Reference in a new issue