# GitLab Releases with CalVer Auto-Versioning — Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Replace MinIO binary distribution with GitLab Generic Package Registry + Releases, with CalVer auto-tagging on every code push to main. **Architecture:** Argo CI pipeline creates a CalVer git tag, compiles binaries, uploads them to GitLab's Generic Package Registry, creates a GitLab Release with auto-generated notes, then patches service deployments to fetch from GitLab. MinIO retains sccache, training data, and model checkpoints. **Tech Stack:** Argo Workflows, GitLab CE API v4, Generic Package Registry, K8s strategic merge patches, curl --- ## Context **GitLab internal URL:** `http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181` **GitLab project ID:** `1` (root/foxhunt) **Existing secrets:** `gitlab-pat` (auto-rotated PAT, monthly), `minio-credentials` **Existing file:** `crates/common/src/build_info.rs` — already captures `FOXHUNT_BUILD_VERSION` at compile time **Web dashboard:** stays on MinIO (not code, not part of release) **Services affected (7):** api, trading-service, ml-training-service, backtesting-service, trading-agent-service, broker-gateway, data-acquisition-service **Binary names from cargo build:** - Services: `api`, `trading-service`, `ml-training-service`, `backtesting-service`, `trading-agent-service`, `broker-gateway`, `data-acquisition-service` - Training: `train_baseline_rl`, `train_baseline_supervised`, `evaluate_baseline`, `evaluate_supervised`, `hyperopt_baseline_rl`, `hyperopt_baseline_supervised`, `training_uploader` --- ### Task 1: Create GitLab deploy token and K8s secret This is a one-time manual setup. The deploy token lets service pods download packages from GitLab's Generic Package Registry (read-only). **Step 1: Create deploy token via GitLab API** Run from a machine with access to the cluster: ```bash # Use existing PAT to create a project deploy token GITLAB="http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181" PAT=$(kubectl -n foxhunt get secret gitlab-pat -o jsonpath='{.data.token}' | base64 -d) curl -sf -X POST "${GITLAB}/api/v4/projects/1/deploy_tokens" \ -H "PRIVATE-TOKEN: ${PAT}" \ -d "name=foxhunt-package-reader" \ -d "scopes[]=read_package_registry" | jq . ``` Save the returned `token` value. **Step 2: Create K8s secret** ```bash kubectl -n foxhunt create secret generic gitlab-deploy-token \ --from-literal=token= ``` **Step 3: Verify token can read packages** ```bash # Should return 200 (empty list is fine — no packages yet) curl -sf -o /dev/null -w "%{http_code}" \ -H "DEPLOY-TOKEN: " \ "${GITLAB}/api/v4/projects/1/packages" ``` Expected: `200` **Step 4: Verify existing gitlab-pat has api scope** ```bash curl -sf "${GITLAB}/api/v4/personal_access_tokens/self" \ -H "PRIVATE-TOKEN: ${PAT}" | jq '.scopes' ``` Expected: scopes must include `api` (for creating tags, releases, uploading packages). If not, recreate the PAT with `api` scope. --- ### Task 2: Add `needs-code` output to detect-changes **Files:** - Modify: `infra/k8s/argo/ci-pipeline-template.yaml:191-220` (detect-changes script outputs) **Step 1: Add needs-code computation to detect-changes script** In the detect-changes shell script, after the `NEEDS_TRAINING` computation (line ~207) and before the `echo "=== Build decisions ==="` line, add: ```sh if [ "$NEEDS_SERVICES" = "true" ] || [ "$NEEDS_TRAINING" = "true" ]; then NEEDS_CODE="true" else NEEDS_CODE="false" fi ``` And after `echo -n "$DOCKER_IMAGES" > /tmp/outputs/docker-images`, add: ```sh echo -n "$NEEDS_CODE" > /tmp/outputs/needs-code ``` Also add the build decisions echo: ```sh echo "needs-code: $NEEDS_CODE" ``` **Step 2: Add needs-code output parameter** After the `docker-images` output parameter (line ~235), add: ```yaml - name: needs-code valueFrom: path: /tmp/outputs/needs-code ``` **Step 3: Verify YAML is valid** ```bash python3 -c "import yaml; yaml.safe_load(open('infra/k8s/argo/ci-pipeline-template.yaml'))" ``` Expected: no error --- ### Task 3: New `create-tag` template **Files:** - Modify: `infra/k8s/argo/ci-pipeline-template.yaml` (add template after detect-changes, before compile-services) **Step 1: Add create-tag template** Insert after the detect-changes template `outputs:` block (after line ~237) and before the `build-web-dashboard` template: ```yaml # ── create-tag: CalVer auto-tag on code changes ── - name: create-tag nodeSelector: k8s.scaleway.com/pool-name: platform container: image: gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/foxhunt-runtime:latest command: ["/bin/sh", "-c"] env: - name: GITLAB_PAT valueFrom: secretKeyRef: name: gitlab-pat key: token resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi args: - | set -e GITLAB="http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181" PROJECT_ID=1 SHA="{{workflow.parameters.commit-sha}}" # Compute CalVer prefix: vYYYY.MM PREFIX="v$(date +%Y.%m)" # Query existing tags for this month TAGS=$(curl -sf \ -H "PRIVATE-TOKEN: ${GITLAB_PAT}" \ "${GITLAB}/api/v4/projects/${PROJECT_ID}/repository/tags?search=${PREFIX}" \ || echo "[]") # Parse highest N from vYYYY.MM.N tags LAST_N=$(echo "$TAGS" | grep -oP "\"name\":\"${PREFIX}\.\K[0-9]+" | sort -n | tail -1) if [ -z "$LAST_N" ]; then NEXT_N=1 else NEXT_N=$((LAST_N + 1)) fi TAG="${PREFIX}.${NEXT_N}" echo "Creating tag: ${TAG} at ${SHA}" # Create the tag RESULT=$(curl -sf -X POST \ -H "PRIVATE-TOKEN: ${GITLAB_PAT}" \ -d "tag_name=${TAG}" \ -d "ref=${SHA}" \ "${GITLAB}/api/v4/projects/${PROJECT_ID}/repository/tags") echo "$RESULT" | grep -q "$TAG" || { echo "Tag creation failed: $RESULT"; exit 1; } echo "Tag ${TAG} created successfully" mkdir -p /tmp/outputs echo -n "$TAG" > /tmp/outputs/tag outputs: parameters: - name: tag valueFrom: path: /tmp/outputs/tag ``` **Step 2: Add gitlab-pat volume to workflow spec** In the `spec.volumes` list (around line 22-32), add: ```yaml - name: gitlab-pat secret: secretName: gitlab-pat ``` **Step 3: Verify YAML** ```bash python3 -c "import yaml; yaml.safe_load(open('infra/k8s/argo/ci-pipeline-template.yaml'))" ``` Expected: no error --- ### Task 4: Modify compile-services — version from tag + upload to GitLab packages **Files:** - Modify: `infra/k8s/argo/ci-pipeline-template.yaml` (compile-services template, lines ~309-426) **Step 1: Replace version computation** Find and replace the 3 lines (around line 393-395): ```sh YEAR=$(date +%Y) MONTH=$(date +%m) export FOXHUNT_BUILD_VERSION="${YEAR}.${MONTH}.argo" ``` With: ```sh export FOXHUNT_BUILD_VERSION="{{tasks.create-tag.outputs.parameters.tag}}" ``` **Step 2: Replace MinIO upload with GitLab package upload** Find and replace the rclone upload block (around lines 418-424): ```sh echo "=== Uploading service binaries ===" rclone copy "$WORKSPACE/bin/services/" :s3:foxhunt-binaries/services/ \ --s3-provider=Minio \ --s3-endpoint=http://minio.foxhunt.svc.cluster.local:9000 \ --s3-access-key-id="${MINIO_ACCESS_KEY}" \ --s3-secret-access-key="${MINIO_SECRET_KEY}" \ --s3-no-check-bucket ``` With: ```sh echo "=== Uploading service binaries to GitLab packages ===" GITLAB="http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181" TAG="${FOXHUNT_BUILD_VERSION}" for bin in "$WORKSPACE/bin/services/"*; do BIN_NAME=$(basename "$bin") echo "Uploading ${BIN_NAME}..." curl -f --upload-file "$bin" \ -H "PRIVATE-TOKEN: ${GITLAB_PAT}" \ "${GITLAB}/api/v4/projects/1/packages/generic/foxhunt-services/${TAG}/${BIN_NAME}" done ``` **Step 3: Add GITLAB_PAT env var, remove MINIO upload env vars** In the compile-services container `env:` list, add: ```yaml - name: GITLAB_PAT valueFrom: secretKeyRef: name: gitlab-pat key: token ``` Remove the `MINIO_ACCESS_KEY` and `MINIO_SECRET_KEY` env entries (keep `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` — they're needed by sccache). **Step 4: Verify YAML** ```bash python3 -c "import yaml; yaml.safe_load(open('infra/k8s/argo/ci-pipeline-template.yaml'))" ``` --- ### Task 5: Modify compile-training — version from tag + upload to GitLab packages **Files:** - Modify: `infra/k8s/argo/ci-pipeline-template.yaml` (compile-training template, lines ~430-547) **Step 1: Replace version computation** Find and replace (around line 513-514): ```sh YEAR=$(date +%Y) MONTH=$(date +%m) export FOXHUNT_BUILD_VERSION="${YEAR}.${MONTH}.argo" ``` With: ```sh export FOXHUNT_BUILD_VERSION="{{tasks.create-tag.outputs.parameters.tag}}" ``` **Step 2: Replace MinIO upload with GitLab package upload** Find and replace the rclone upload block (around lines 539-545): ```sh echo "=== Uploading training binaries ===" rclone copy "$WORKSPACE/bin/training/" :s3:foxhunt-binaries/training/ \ --s3-provider=Minio \ --s3-endpoint=http://minio.foxhunt.svc.cluster.local:9000 \ --s3-access-key-id="${MINIO_ACCESS_KEY}" \ --s3-secret-access-key="${MINIO_SECRET_KEY}" \ --s3-no-check-bucket ``` With: ```sh echo "=== Uploading training binaries to GitLab packages ===" GITLAB="http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181" TAG="${FOXHUNT_BUILD_VERSION}" for bin in "$WORKSPACE/bin/training/"*; do BIN_NAME=$(basename "$bin") echo "Uploading ${BIN_NAME}..." curl -f --upload-file "$bin" \ -H "PRIVATE-TOKEN: ${GITLAB_PAT}" \ "${GITLAB}/api/v4/projects/1/packages/generic/foxhunt-training/${TAG}/${BIN_NAME}" done ``` **Step 3: Add GITLAB_PAT env var, remove MINIO upload env vars** Same as Task 4 Step 3. Add `GITLAB_PAT` from `gitlab-pat` secret. Remove `MINIO_ACCESS_KEY` and `MINIO_SECRET_KEY` (keep `AWS_*` for sccache). **Step 4: Verify YAML** ```bash python3 -c "import yaml; yaml.safe_load(open('infra/k8s/argo/ci-pipeline-template.yaml'))" ``` --- ### Task 6: New `upload-release` template **Files:** - Modify: `infra/k8s/argo/ci-pipeline-template.yaml` (add template after compile-training, before gpu-warmup) **Step 1: Add upload-release template** ```yaml # ── upload-release: create GitLab Release with package links ── - name: upload-release nodeSelector: k8s.scaleway.com/pool-name: platform container: image: gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/foxhunt-runtime:latest command: ["/bin/sh", "-c"] env: - name: GITLAB_PAT valueFrom: secretKeyRef: name: gitlab-pat key: token resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi args: - | set -e GITLAB="http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181" PROJECT_ID=1 TAG="{{tasks.create-tag.outputs.parameters.tag}}" echo "=== Creating GitLab Release ${TAG} ===" # Get commits since previous tag for release notes PREV_TAG=$(curl -sf \ -H "PRIVATE-TOKEN: ${GITLAB_PAT}" \ "${GITLAB}/api/v4/projects/${PROJECT_ID}/repository/tags?per_page=2" \ | grep -oP '"name":"\K[^"]+' | sed -n '2p') if [ -n "$PREV_TAG" ]; then COMMITS=$(curl -sf \ -H "PRIVATE-TOKEN: ${GITLAB_PAT}" \ "${GITLAB}/api/v4/projects/${PROJECT_ID}/repository/compare?from=${PREV_TAG}&to=${TAG}" \ | grep -oP '"title":"\K[^"]+' | head -20 \ | sed 's/^/- /' || echo "- Release ${TAG}") DESCRIPTION="## Changes since ${PREV_TAG}\n\n${COMMITS}" else DESCRIPTION="## Initial release\n\nFirst CalVer release." fi # Create the release RESPONSE=$(curl -sf -X POST \ -H "PRIVATE-TOKEN: ${GITLAB_PAT}" \ -H "Content-Type: application/json" \ -d "{ \"tag_name\": \"${TAG}\", \"name\": \"${TAG}\", \"description\": \"$(printf '%s' "$DESCRIPTION" | sed 's/"/\\"/g')\" }" \ "${GITLAB}/api/v4/projects/${PROJECT_ID}/releases") || { echo "WARN: Release creation failed (may already exist)" } echo "Release ${TAG} created" echo "$RESPONSE" | head -5 # Add package links as release assets PKG_URL="${GITLAB}/api/v4/projects/${PROJECT_ID}/packages/generic" for pkg_name in foxhunt-services foxhunt-training; do # Check if package exists for this tag PKG_CHECK=$(curl -sf \ -H "PRIVATE-TOKEN: ${GITLAB_PAT}" \ "${GITLAB}/api/v4/projects/${PROJECT_ID}/packages?package_name=${pkg_name}&package_version=${TAG}" \ || echo "[]") if echo "$PKG_CHECK" | grep -q "$TAG"; then curl -sf -X POST \ -H "PRIVATE-TOKEN: ${GITLAB_PAT}" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"${pkg_name}\", \"url\": \"${GITLAB}/-/packages?type=generic&search=${pkg_name}&version=${TAG}\", \"link_type\": \"package\" }" \ "${GITLAB}/api/v4/projects/${PROJECT_ID}/releases/${TAG}/assets/links" || true echo "Linked ${pkg_name} package to release" fi done echo "=== Release ${TAG} complete ===" ``` **Step 2: Verify YAML** ```bash python3 -c "import yaml; yaml.safe_load(open('infra/k8s/argo/ci-pipeline-template.yaml'))" ``` --- ### Task 7: Modify deploy-services — patch FOXHUNT_RELEASE on deployments **Files:** - Modify: `infra/k8s/argo/ci-pipeline-template.yaml` (deploy-services template, lines ~582-620) **Step 1: Replace deploy-services script** Replace the entire `args` block of deploy-services with: ```sh set -e if ! command -v kubectl >/dev/null 2>&1; then echo "=== Installing kubectl ===" curl -sLo /tmp/kubectl "https://dl.k8s.io/release/v1.31.4/bin/linux/amd64/kubectl" chmod +x /tmp/kubectl export PATH="/tmp:$PATH" fi TAG="{{tasks.create-tag.outputs.parameters.tag}}" echo "=== Deploying release ${TAG} ===" SERVICES="api trading-service ml-training-service backtesting-service trading-agent-service broker-gateway data-acquisition-service" for svc in $SERVICES; do echo "Patching $svc with FOXHUNT_RELEASE=${TAG}..." kubectl -n foxhunt patch deployment "$svc" -p "{ \"spec\":{\"template\":{ \"metadata\":{\"annotations\":{\"foxhunt.io/release\":\"${TAG}\"}}, \"spec\":{\"initContainers\":[{ \"name\":\"fetch-binary\", \"env\":[{\"name\":\"FOXHUNT_RELEASE\",\"value\":\"${TAG}\"}] }]} }} }" || echo "WARN: $svc patch failed (may not exist)" done echo "=== Waiting for rollouts ===" for svc in $SERVICES; do kubectl -n foxhunt rollout status deployment "$svc" --timeout=120s || echo "WARN: $svc rollout timeout" done echo "=== Deploy ${TAG} complete ===" ``` **Step 2: Verify YAML** ```bash python3 -c "import yaml; yaml.safe_load(open('infra/k8s/argo/ci-pipeline-template.yaml'))" ``` --- ### Task 8: Update DAG dependencies **Files:** - Modify: `infra/k8s/argo/ci-pipeline-template.yaml` (DAG tasks, lines ~44-136) **Step 1: Replace the DAG tasks section** Replace the entire `dag.tasks` list with: ```yaml tasks: - name: detect-changes template: detect-changes # Create CalVer tag when code changed - name: create-tag dependencies: [detect-changes] template: create-tag when: "{{tasks.detect-changes.outputs.parameters.needs-code}} == true" # Parallel: each compile step is self-contained (own git clone) # sccache backed by MinIO — no PVC needed, shared across all nodes - name: compile-services dependencies: [create-tag] template: compile-services when: "{{tasks.detect-changes.outputs.parameters.needs-services}} == true" - name: compile-training dependencies: [create-tag] template: compile-training when: "{{tasks.detect-changes.outputs.parameters.needs-training}} == true" # GPU warmup: trigger GPU node autoscale during compile so the node # is ready when we submit a training workflow after CI completes. - name: gpu-warmup dependencies: [detect-changes] template: gpu-warmup when: "{{tasks.detect-changes.outputs.parameters.needs-training}} == true" - name: build-web-dashboard dependencies: [detect-changes] template: build-web-dashboard when: "{{tasks.detect-changes.outputs.parameters.needs-dashboard}} == true" # Create GitLab Release after all binaries are uploaded - name: upload-release dependencies: [compile-services, compile-training] template: upload-release when: "{{tasks.detect-changes.outputs.parameters.needs-code}} == true" # Deploy after release is created - name: deploy-services dependencies: [upload-release] template: deploy-services when: "{{tasks.detect-changes.outputs.parameters.needs-services}} == true" - name: rebuild-ci-builder dependencies: [detect-changes] templateRef: name: build-ci-image template: build arguments: parameters: - name: commit-sha value: "{{workflow.parameters.commit-sha}}" - name: dockerfile value: Dockerfile.ci-builder - name: image-name value: ci-builder when: "{{tasks.detect-changes.outputs.parameters.docker-images}} == true" - name: rebuild-ci-builder-cpu dependencies: [detect-changes] templateRef: name: build-ci-image template: build arguments: parameters: - name: commit-sha value: "{{workflow.parameters.commit-sha}}" - name: dockerfile value: Dockerfile.ci-builder-cpu - name: image-name value: ci-builder-cpu when: "{{tasks.detect-changes.outputs.parameters.docker-images}} == true" - name: rebuild-runtime dependencies: [detect-changes] templateRef: name: build-ci-image template: build arguments: parameters: - name: commit-sha value: "{{workflow.parameters.commit-sha}}" - name: dockerfile value: Dockerfile.foxhunt-runtime - name: image-name value: foxhunt-runtime when: "{{tasks.detect-changes.outputs.parameters.docker-images}} == true" - name: rebuild-training-runtime dependencies: [detect-changes] templateRef: name: build-ci-image template: build arguments: parameters: - name: commit-sha value: "{{workflow.parameters.commit-sha}}" - name: dockerfile value: Dockerfile.foxhunt-training-runtime - name: image-name value: foxhunt-training-runtime when: "{{tasks.detect-changes.outputs.parameters.docker-images}} == true" ``` **Step 2: Verify the full pipeline YAML** ```bash python3 -c "import yaml; yaml.safe_load(open('infra/k8s/argo/ci-pipeline-template.yaml'))" ``` --- ### Task 9: Update service initContainers — fetch from GitLab **Files (7 service deployments):** - Modify: `infra/k8s/services/api.yaml:41-78` - Modify: `infra/k8s/services/trading-service.yaml:41-77` - Modify: `infra/k8s/services/ml-training-service.yaml:83-120` (offset by SA/RBAC) - Modify: `infra/k8s/services/backtesting-service.yaml` (same initContainer pattern) - Modify: `infra/k8s/services/broker-gateway.yaml` (same initContainer pattern) - Modify: `infra/k8s/services/data-acquisition-service.yaml` (same initContainer pattern) - Modify: `infra/k8s/services/trading-agent-service.yaml` (same initContainer pattern) Each file has the same initContainer pattern. Replace the `fetch-binary` initContainer in all 7 files. **Step 1: Replace initContainer in each service** For each service, replace the `fetch-binary` initContainer block. The template (substitute `SERVICE_NAME` for each): ```yaml - name: fetch-binary image: gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/foxhunt-runtime:latest command: ["/bin/sh", "-c"] args: - | set -e BINARY="SERVICE_NAME" curl -fSL -o "/binaries/${BINARY}" \ --header "DEPLOY-TOKEN: ${GITLAB_DEPLOY_TOKEN}" \ "${GITLAB_API}/projects/1/packages/generic/foxhunt-services/${FOXHUNT_RELEASE}/${BINARY}" chmod +x "/binaries/${BINARY}" echo "Fetched ${BINARY} ${FOXHUNT_RELEASE} ($(stat -c%s /binaries/${BINARY}) bytes)" env: - name: GITLAB_DEPLOY_TOKEN valueFrom: secretKeyRef: name: gitlab-deploy-token key: token - name: GITLAB_API value: "http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181/api/v4" - name: FOXHUNT_RELEASE value: "latest" volumeMounts: - name: binaries mountPath: /binaries resources: requests: cpu: 100m memory: 64Mi limits: cpu: 500m memory: 128Mi ``` Substitutions per file: - `api.yaml`: `SERVICE_NAME` = `api` - `trading-service.yaml`: `SERVICE_NAME` = `trading-service` - `ml-training-service.yaml`: `SERVICE_NAME` = `ml-training-service` - `backtesting-service.yaml`: `SERVICE_NAME` = `backtesting-service` - `trading-agent-service.yaml`: `SERVICE_NAME` = `trading-agent-service` - `broker-gateway.yaml`: `SERVICE_NAME` = `broker-gateway` - `data-acquisition-service.yaml`: `SERVICE_NAME` = `data-acquisition-service` Note: The `FOXHUNT_RELEASE` value `"latest"` is a placeholder — the deploy-services CI step patches it to the actual tag via `kubectl patch`. **Step 2: Verify all 7 service YAMLs** ```bash for f in infra/k8s/services/{api,trading-service,ml-training-service,backtesting-service,trading-agent-service,broker-gateway,data-acquisition-service}.yaml; do python3 -c "import yaml; list(yaml.safe_load_all(open('$f')))" && echo "$f OK" || echo "$f FAILED" done ``` Expected: all OK --- ### Task 10: Update training job-template — binary fetch from GitLab **Files:** - Modify: `infra/k8s/training/job-template.yaml:38-78` (fetch-binary initContainer only; sync-training-data stays MinIO) **Step 1: Replace fetch-binary initContainer** Replace the first initContainer (`fetch-binary`, lines 39-78) with: ```yaml - name: fetch-binary image: gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/foxhunt-training-runtime:latest command: ["/bin/sh", "-c"] args: - | set -e BINARY="$(TRAINING_BINARY)" curl -fSL -o "/binaries/${BINARY}" \ --header "DEPLOY-TOKEN: ${GITLAB_DEPLOY_TOKEN}" \ "${GITLAB_API}/projects/1/packages/generic/foxhunt-training/${FOXHUNT_RELEASE}/${BINARY}" chmod +x "/binaries/${BINARY}" echo "Fetched ${BINARY} ${FOXHUNT_RELEASE} ($(stat -c%s /binaries/${BINARY}) bytes)" env: - name: TRAINING_BINARY value: train_baseline_supervised - name: GITLAB_DEPLOY_TOKEN valueFrom: secretKeyRef: name: gitlab-deploy-token key: token - name: GITLAB_API value: "http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181/api/v4" - name: FOXHUNT_RELEASE value: "latest" volumeMounts: - name: binaries mountPath: /binaries resources: requests: cpu: 100m memory: 64Mi limits: cpu: 500m memory: 128Mi ``` Note: `sync-training-data` initContainer stays unchanged (MinIO). Only binary fetch moves to GitLab. **Step 2: Verify YAML** ```bash python3 -c "import yaml; yaml.safe_load(open('infra/k8s/training/job-template.yaml'))" ``` --- ### Task 11: Commit and apply to cluster **Step 1: Commit all changes** ```bash git add infra/k8s/argo/ci-pipeline-template.yaml \ infra/k8s/services/api.yaml \ infra/k8s/services/trading-service.yaml \ infra/k8s/services/ml-training-service.yaml \ infra/k8s/services/backtesting-service.yaml \ infra/k8s/services/trading-agent-service.yaml \ infra/k8s/services/broker-gateway.yaml \ infra/k8s/services/data-acquisition-service.yaml \ infra/k8s/training/job-template.yaml git commit -m "feat(infra): GitLab releases with CalVer auto-versioning Replace MinIO binary distribution with GitLab Generic Package Registry. Every code push to main auto-creates a CalVer tag (vYYYY.MM.N), compiles, uploads binaries to GitLab packages, creates a Release with auto-generated notes, and deploys via deployment patching. - New CI templates: create-tag, upload-release - Modified: compile-services/training upload to GitLab packages - Modified: deploy-services patches FOXHUNT_RELEASE on deployments - All 7 service initContainers fetch from GitLab (curl, deploy token) - Training job-template binary fetch from GitLab (data stays MinIO) - MinIO retains: sccache, training data, model checkpoints" ``` **Step 2: Apply CI pipeline template** ```bash kubectl apply -f infra/k8s/argo/ci-pipeline-template.yaml ``` **Step 3: Apply service deployments (will NOT restart — just updates spec)** ```bash kubectl apply -f infra/k8s/services/api.yaml kubectl apply -f infra/k8s/services/trading-service.yaml kubectl apply -f infra/k8s/services/ml-training-service.yaml kubectl apply -f infra/k8s/services/backtesting-service.yaml kubectl apply -f infra/k8s/services/trading-agent-service.yaml kubectl apply -f infra/k8s/services/broker-gateway.yaml kubectl apply -f infra/k8s/services/data-acquisition-service.yaml ``` Note: Don't apply job-template.yaml — it's a template that is applied per-job, not a persistent resource. **Step 4: Push to trigger first release** ```bash git push origin main ``` **Step 5: Monitor the Argo workflow** ```bash # Watch the workflow kubectl -n foxhunt get workflows -w # Check tag was created kubectl -n foxhunt logs -l workflows.argoproj.io/workflow -c main --tail=50 | grep "Creating tag" ``` Expected: workflow creates tag `v2026.03.1`, compiles, uploads to GitLab packages, creates release, patches deployments. **Step 6: Verify services restarted with new binary** ```bash kubectl -n foxhunt get pods -o wide kubectl -n foxhunt logs deployment/api -c fetch-binary ``` Expected: initContainer logs show `Fetched api v2026.03.1 (... bytes)` --- ### Task 12: Cleanup — remove MinIO binary bucket Only do this after Task 11 is verified working. **Step 1: Verify GitLab packages exist** ```bash GITLAB="http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181" PAT=$(kubectl -n foxhunt get secret gitlab-pat -o jsonpath='{.data.token}' | base64 -d) curl -sf -H "PRIVATE-TOKEN: ${PAT}" \ "${GITLAB}/api/v4/projects/1/packages?package_type=generic" | python3 -m json.tool ``` Expected: lists foxhunt-services and foxhunt-training packages with the tag version. **Step 2: Remove binary bucket contents from MinIO** ```bash kubectl -n foxhunt exec deployment/minio -- mc rm --recursive --force /data/foxhunt-binaries/services/ kubectl -n foxhunt exec deployment/minio -- mc rm --recursive --force /data/foxhunt-binaries/training/ ``` Keep the bucket itself (other paths may reference it). **Step 3: Commit cleanup note** No code changes needed — the MinIO upload code was already removed in Task 4/5.