feat: wire UnityRetryService into the local provider's build retry (o… #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Integration Tests | |
| on: | |
| schedule: | |
| - cron: '0 3 * * *' # Nightly at 3 AM UTC | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'plugins/orchestrator/src/model/orchestrator/providers/**' | |
| - 'plugins/orchestrator/src/model/orchestrator/orchestrator.ts' | |
| - 'plugins/orchestrator/src/model/orchestrator/workflows/**' | |
| - 'plugins/orchestrator/src/model/orchestrator/services/**' | |
| - '.github/workflows/orchestrator-integrity.yml' | |
| workflow_dispatch: | |
| inputs: | |
| runGithubIntegrationTests: | |
| description: 'Run GitHub Checks integration tests' | |
| required: false | |
| default: 'false' | |
| type: string | |
| workflow_call: | |
| inputs: | |
| runGithubIntegrationTests: | |
| description: 'Run GitHub Checks integration tests' | |
| required: false | |
| default: 'false' | |
| type: string | |
| permissions: | |
| contents: read | |
| checks: write | |
| statuses: write | |
| env: | |
| AWS_STACK_NAME: game-ci-team-pipelines | |
| ORCHESTRATOR_BRANCH: ${{ github.ref }} | |
| DEBUG: true | |
| PROJECT_PATH: test-project | |
| USE_IL2CPP: false | |
| ORCHESTRATOR_AWS_STACK_WAIT_TIME: 900 | |
| # Port of plugins/orchestrator/.github/workflows/orchestrator-integrity.yml, | |
| # inert as a nested workflow. `run:` steps execute inside plugins/orchestrator | |
| # via this default; `uses:` steps (checkout, cache, setup-kubectl) are | |
| # unaffected by working-directory and stay repo-root-relative, which is | |
| # correct for them. | |
| defaults: | |
| run: | |
| working-directory: plugins/orchestrator | |
| # ============================================================================== | |
| # Parallel job architecture | |
| # ============================================================================== | |
| # Previously a single 3+ hour monolith job. Now split into 4 parallel jobs, each | |
| # on its own runner with a fresh 14GB disk. This: | |
| # - Cuts wall-clock time from ~3h to ~1h (longest single job) | |
| # - Eliminates disk exhaustion (no shared disk between provider strategies) | |
| # - Deduplicates cleanup logic via reusable shell functions | |
| # | |
| # Job groups: | |
| # k8s-tests - Needs k3d cluster + MiniStack. 5 tests. | |
| # aws-provider-tests - Needs MiniStack only (no k3d). 8 tests. | |
| # local-docker-tests - Needs Docker only (some tests also need MiniStack). 10 tests. | |
| # rclone-tests - Needs rclone + MiniStack. 1 test. | |
| # ============================================================================== | |
| jobs: | |
| # ============================================================================ | |
| # K8S TESTS | |
| # ============================================================================ | |
| k8s-tests: | |
| name: K8s Provider Tests | |
| runs-on: ubuntu-latest | |
| env: | |
| K3D_NODE_CONTAINERS: 'k3d-unity-builder-agent-0' | |
| AWS_FORCE_PROVIDER: aws-local | |
| RESOURCE_TRACKING: 'true' | |
| K8S_MINISTACK_HOST: localstack-main | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT_URL: http://localhost:4566 | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| steps: | |
| # --- Setup --- | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: false | |
| - name: Install package manager (from package.json) | |
| run: | | |
| corepack enable | |
| corepack install | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Resolve yarn cache folder | |
| id: yarn-config | |
| run: echo "cacheFolder=$(yarn config get cacheFolder)" >> "$GITHUB_OUTPUT" | |
| - name: Restore yarn install cache (node_modules + cacheFolder + install-state) | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| plugins/orchestrator/node_modules | |
| ${{ steps.yarn-config.outputs.cacheFolder }} | |
| plugins/orchestrator/.yarn/install-state.gz | |
| key: yarn-${{ runner.os }}-node-20-${{ hashFiles('plugins/orchestrator/yarn.lock') }} | |
| restore-keys: | | |
| yarn-${{ runner.os }}-node-20- | |
| - name: Set up kubectl | |
| uses: azure/setup-kubectl@v4 | |
| with: | |
| version: 'v1.34.1' | |
| - name: Install k3d | |
| run: | | |
| curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash | |
| k3d version | cat | |
| - name: Define cleanup functions | |
| run: | | |
| # Write reusable cleanup functions to a script sourced by later steps | |
| cat > /tmp/cleanup-functions.sh << 'CLEANUP_EOF' | |
| light_cleanup() { | |
| echo "--- Light cleanup ---" | |
| rm -rf ./orchestrator-cache/* || true | |
| docker system prune -f || true | |
| df -h | |
| } | |
| k8s_resource_cleanup() { | |
| echo "--- K8s resource cleanup ---" | |
| kubectl delete jobs --all --ignore-not-found=true -n default || true | |
| kubectl get pods -n default -o name 2>/dev/null | grep -E "(unity-builder-job-|helper-pod-)" | while IFS= read -r pod; do | |
| kubectl delete "$pod" --ignore-not-found=true || true | |
| done || true | |
| kubectl get pvc -n default -o name 2>/dev/null | grep "unity-builder-pvc-" | while read pvc; do | |
| kubectl delete "$pvc" --ignore-not-found=true || true | |
| done || true | |
| kubectl get secrets -n default -o name 2>/dev/null | grep "build-credentials-" | while read secret; do | |
| kubectl delete "$secret" --ignore-not-found=true || true | |
| done || true | |
| } | |
| k3d_node_cleanup() { | |
| echo "--- K3d node image cleanup (preserving Unity images) ---" | |
| K3D_NODE_CONTAINERS="${K3D_NODE_CONTAINERS:-k3d-unity-builder-agent-0 k3d-unity-builder-server-0}" | |
| for NODE in $K3D_NODE_CONTAINERS; do | |
| docker exec "$NODE" sh -c "crictl rm --all 2>/dev/null || true" || true | |
| docker exec "$NODE" sh -c "for img in \$(crictl images -q 2>/dev/null); do repo=\$(crictl inspecti \$img --format '{{.repo}}' 2>/dev/null || echo ''); if echo \"\$repo\" | grep -qvE 'unityci/editor|unity'; then crictl rmi \$img 2>/dev/null || true; fi; done" || true | |
| docker exec "$NODE" sh -c "crictl rmi --prune 2>/dev/null || true" || true | |
| done || true | |
| } | |
| full_k8s_cleanup() { | |
| k8s_resource_cleanup | |
| k3d_node_cleanup | |
| light_cleanup | |
| } | |
| CLEANUP_EOF | |
| echo "Cleanup functions defined at /tmp/cleanup-functions.sh" | |
| - name: Initial disk space cleanup | |
| run: | | |
| echo "Initial disk space cleanup..." | |
| df -h | |
| k3d cluster delete unity-builder || true | |
| docker stop localstack-main 2>/dev/null || true | |
| docker rm localstack-main 2>/dev/null || true | |
| docker system prune -af --volumes || true | |
| docker network rm orchestrator-net 2>/dev/null || true | |
| docker network create orchestrator-net || true | |
| echo "Disk usage after cleanup:" | |
| df -h | |
| - name: Start MiniStack | |
| run: | | |
| echo "Starting MiniStack..." | |
| HOST_IP=$(ip route | grep default | awk '{print $3}') | |
| echo "Host gateway IP: $HOST_IP" | |
| docker run -d \ | |
| --name localstack-main \ | |
| --network orchestrator-net \ | |
| --add-host=host.docker.internal:host-gateway \ | |
| -p 4566:4566 \ | |
| -e SERVICES=s3,cloudformation,ecs,kinesis,cloudwatch,logs,efs,ec2,iam,elasticfilesystem,secretsmanager,lambda,events,sts \ | |
| -e DEBUG=0 \ | |
| -e HOSTNAME_EXTERNAL=localstack-main \ | |
| ministackorg/ministack || true | |
| echo "Waiting for MiniStack to be ready..." | |
| MAX_ATTEMPTS=60 | |
| READY=false | |
| for i in $(seq 1 $MAX_ATTEMPTS); do | |
| if ! docker ps | grep -q localstack-main; then | |
| echo "MiniStack container not running (attempt $i/$MAX_ATTEMPTS)" | |
| sleep 2 | |
| continue | |
| fi | |
| if curl -sf http://localhost:4566 > /dev/null 2>&1; then | |
| echo "MiniStack is ready (attempt $i/$MAX_ATTEMPTS)" | |
| READY=true | |
| break | |
| fi | |
| echo "Waiting for MiniStack... ($i/$MAX_ATTEMPTS)" | |
| sleep 2 | |
| done | |
| if [ "$READY" != "true" ]; then | |
| echo "ERROR: MiniStack did not become ready after $MAX_ATTEMPTS attempts" | |
| docker ps -a | grep localstack || echo "No MiniStack container found" | |
| docker logs localstack-main --tail 100 || true | |
| exit 1 | |
| fi | |
| - name: Install AWS CLI tools | |
| run: | | |
| if ! command -v aws > /dev/null 2>&1; then | |
| pip install awscli || true | |
| fi | |
| aws --version || echo "AWS CLI not available" | |
| - name: Create S3 bucket for tests | |
| run: | | |
| echo "Verifying MiniStack connectivity..." | |
| for i in {1..10}; do | |
| if curl -sf http://localhost:4566 > /dev/null 2>&1; then | |
| echo "MiniStack is accessible" | |
| break | |
| fi | |
| echo "Waiting for MiniStack... ($i/10)" | |
| sleep 1 | |
| done | |
| MAX_RETRIES=5 | |
| RETRY_COUNT=0 | |
| BUCKET_CREATED=false | |
| while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ "$BUCKET_CREATED" != "true" ]; do | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| echo "Attempting to create S3 bucket (attempt $RETRY_COUNT/$MAX_RETRIES)..." | |
| if command -v aws > /dev/null 2>&1; then | |
| if aws --endpoint-url=http://localhost:4566 s3 mb s3://"$AWS_STACK_NAME" 2>&1; then | |
| echo "Bucket created successfully with aws CLI" | |
| BUCKET_CREATED=true | |
| else | |
| echo "Bucket creation failed, will retry..." | |
| sleep 2 | |
| fi | |
| else | |
| echo "aws CLI not available" | |
| exit 1 | |
| fi | |
| done | |
| if [ "$BUCKET_CREATED" != "true" ]; then | |
| echo "ERROR: Failed to create S3 bucket after $MAX_RETRIES attempts" | |
| docker logs localstack-main --tail 50 || true | |
| exit 1 | |
| fi | |
| - run: yarn install --immutable | |
| - run: yarn build | |
| # ========================================== | |
| # FAST UNIT TESTS (no infra required, fast-fail gate) | |
| # ========================================== | |
| - name: Run orchestrator unit tests (fast, no infra) | |
| timeout-minutes: 2 | |
| # vitest positional filters are separate arguments (not pipe-separated). | |
| # Each argument is matched as a substring against the test file path. | |
| run: >- | |
| yarn vitest run | |
| "orchestrator-guid" | |
| "orchestrator-folders" | |
| "task-parameter-serializer" | |
| "follow-log-stream-service" | |
| "runner-availability-service" | |
| "provider-url-parser" | |
| "provider-loader" | |
| "provider-git-manager" | |
| "orchestrator-image" | |
| "orchestrator-hooks" | |
| "orchestrator-github-checks" | |
| --reporter=verbose --no-file-parallelism | |
| # ========================================== | |
| # K8S TESTS SECTION | |
| # ========================================== | |
| - name: Clean up disk space before K8s tests | |
| run: | | |
| echo "Cleaning up disk space before K8s tests..." | |
| rm -rf ./orchestrator-cache/* || true | |
| sudo apt-get clean || true | |
| docker system prune -f || true | |
| df -h | |
| - name: Create k3s cluster (k3d) | |
| timeout-minutes: 5 | |
| run: | | |
| MINISTACK_IP=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' localstack-main 2>/dev/null || echo "") | |
| echo "MiniStack container IP: $MINISTACK_IP" | |
| k3d cluster create unity-builder \ | |
| --agents 1 \ | |
| --network orchestrator-net \ | |
| --wait | |
| kubectl config current-context | cat | |
| echo "MINISTACK_IP=$MINISTACK_IP" >> "$GITHUB_ENV" | |
| - name: Verify cluster readiness and MiniStack connectivity | |
| timeout-minutes: 2 | |
| run: | | |
| for i in {1..60}; do | |
| if kubectl get nodes 2>/dev/null | grep -q Ready; then | |
| echo "Cluster is ready" | |
| break | |
| fi | |
| echo "Waiting for cluster... ($i/60)" | |
| sleep 5 | |
| done | |
| kubectl get nodes | |
| kubectl get storageclass | |
| MINISTACK_IP=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' localstack-main 2>/dev/null || echo "") | |
| echo "MiniStack container IP: $MINISTACK_IP" | |
| echo "Testing MiniStack connectivity from k3d cluster..." | |
| curl -sf --max-time 5 http://localhost:4566 | head -5 || echo "Host connectivity failed" | |
| docker run --rm --network orchestrator-net curlimages/curl \ | |
| curl -sf --max-time 5 http://localstack-main:4566 2>&1 | head -5 || echo "Container network test failed" | |
| kubectl run test-ministack --image=curlimages/curl --rm -i --restart=Never --timeout=30s -- \ | |
| curl -v --max-time 10 http://"${MINISTACK_IP}":4566 2>&1 | head -30 || \ | |
| echo "Cluster connectivity test - if this fails, MiniStack may not be accessible from k3d" | |
| - name: Clean up K8s resources before tests | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| k8s_resource_cleanup | |
| # Wait for PVCs to clear | |
| for i in {1..30}; do | |
| PVC_COUNT=$(kubectl get pvc -n default 2>/dev/null | grep -c "unity-builder-pvc-" || echo "0") | |
| if [ "$PVC_COUNT" -eq 0 ]; then | |
| echo "All PVCs deleted" | |
| break | |
| fi | |
| echo "Waiting for PVCs to be deleted... ($i/30) - Found $PVC_COUNT PVCs" | |
| sleep 1 | |
| done | |
| kubectl get pv 2>/dev/null | grep -E "(Released|Failed)" | awk '{print $1}' | while IFS= read -r pv; do | |
| if [ -n "$pv" ] && [ "$pv" != "NAME" ]; then | |
| kubectl delete pv "$pv" --ignore-not-found=true || true | |
| fi | |
| done || true | |
| sleep 3 | |
| docker system prune -f || true | |
| # --- K8s Test 1: orchestrator-image --- | |
| - name: Run orchestrator-image test (K8s) | |
| timeout-minutes: 10 | |
| run: yarn vitest run "orchestrator-image" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: k8s | |
| KUBE_VOLUME_SIZE: 2Gi | |
| containerCpu: '512' | |
| containerMemory: '512' | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-image (K8s) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| full_k8s_cleanup | |
| # --- K8s Test 2: orchestrator-kubernetes --- | |
| - name: Run orchestrator-kubernetes test | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-kubernetes" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneLinux64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: k8s | |
| KUBE_VOLUME_SIZE: 2Gi | |
| containerCpu: '1000' | |
| containerMemory: '1024' | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT: http://localhost:4566 | |
| INPUT_AWSS3ENDPOINT: http://localhost:4566 | |
| INPUT_AWSENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-kubernetes | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| full_k8s_cleanup | |
| # --- K8s Test 3: orchestrator-s3-steps --- | |
| - name: Run orchestrator-s3-steps test (K8s) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-s3-steps" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneLinux64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: k8s | |
| KUBE_VOLUME_SIZE: 2Gi | |
| containerCpu: '1000' | |
| containerMemory: '1024' | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT: http://localhost:4566 | |
| INPUT_AWSS3ENDPOINT: http://localhost:4566 | |
| INPUT_AWSENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-s3-steps (K8s) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| full_k8s_cleanup | |
| # --- K8s Test 4: orchestrator-end2end-caching --- | |
| - name: Run orchestrator-end2end-caching test (K8s) | |
| timeout-minutes: 60 | |
| run: yarn vitest run "orchestrator-end2end-caching" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneLinux64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: k8s | |
| KUBE_VOLUME_SIZE: 2Gi | |
| containerCpu: '1000' | |
| containerMemory: '1024' | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT: http://localhost:4566 | |
| INPUT_AWSS3ENDPOINT: http://localhost:4566 | |
| INPUT_AWSENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-end2end-caching (K8s) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| full_k8s_cleanup | |
| # --- K8s Test 5: orchestrator-end2end-retaining --- | |
| - name: Heavy cleanup before end2end-retaining | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| k8s_resource_cleanup | |
| k3d_node_cleanup | |
| rm -rf ./orchestrator-cache/* || true | |
| docker system prune -f || true | |
| echo "Disk usage before end2end-retaining test:" | |
| df -h | |
| - name: Run orchestrator-end2end-retaining test (K8s) | |
| timeout-minutes: 60 | |
| run: yarn vitest run "orchestrator-end2end-retaining" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: k8s | |
| KUBE_VOLUME_SIZE: 2Gi | |
| containerCpu: '512' | |
| containerMemory: '512' | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT: http://localhost:4566 | |
| INPUT_AWSS3ENDPOINT: http://localhost:4566 | |
| INPUT_AWSENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| # --- K8s teardown --- | |
| - name: Delete k3d cluster and final cleanup | |
| if: always() | |
| run: | | |
| echo "Deleting k3d cluster..." | |
| k3d cluster delete unity-builder || true | |
| docker stop localstack-main 2>/dev/null || true | |
| docker rm localstack-main 2>/dev/null || true | |
| docker system prune -af --volumes || true | |
| echo "Final disk usage:" | |
| df -h | |
| # ============================================================================ | |
| # AWS/MINISTACK PROVIDER TESTS | |
| # ============================================================================ | |
| aws-provider-tests: | |
| name: AWS Provider Tests | |
| runs-on: ubuntu-latest | |
| env: | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT_URL: http://localhost:4566 | |
| steps: | |
| # --- Setup --- | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: false | |
| - name: Install package manager (from package.json) | |
| run: | | |
| corepack enable | |
| corepack install | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Resolve yarn cache folder | |
| id: yarn-config | |
| run: echo "cacheFolder=$(yarn config get cacheFolder)" >> "$GITHUB_OUTPUT" | |
| - name: Restore yarn install cache (node_modules + cacheFolder + install-state) | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| plugins/orchestrator/node_modules | |
| ${{ steps.yarn-config.outputs.cacheFolder }} | |
| plugins/orchestrator/.yarn/install-state.gz | |
| key: yarn-${{ runner.os }}-node-20-${{ hashFiles('plugins/orchestrator/yarn.lock') }} | |
| restore-keys: | | |
| yarn-${{ runner.os }}-node-20- | |
| - name: Define cleanup functions | |
| run: | | |
| cat > /tmp/cleanup-functions.sh << 'CLEANUP_EOF' | |
| light_cleanup() { | |
| echo "--- Light cleanup ---" | |
| rm -rf ./orchestrator-cache/* || true | |
| docker system prune -f || true | |
| df -h | |
| } | |
| heavy_cleanup() { | |
| echo "--- Heavy cleanup ---" | |
| rm -rf ./orchestrator-cache/* || true | |
| docker system prune -af --volumes || true | |
| df -h | |
| } | |
| CLEANUP_EOF | |
| echo "Cleanup functions defined at /tmp/cleanup-functions.sh" | |
| - name: Initial disk space cleanup | |
| run: | | |
| echo "Initial disk space cleanup..." | |
| df -h | |
| docker system prune -af --volumes || true | |
| echo "Disk usage after cleanup:" | |
| df -h | |
| - name: Start MiniStack | |
| run: | | |
| echo "Starting MiniStack..." | |
| docker run -d \ | |
| --name localstack-main \ | |
| -p 4566:4566 \ | |
| -e SERVICES=s3,cloudformation,ecs,kinesis,cloudwatch,logs,efs,ec2,iam,elasticfilesystem,secretsmanager,lambda,events,sts \ | |
| -e DEBUG=0 \ | |
| ministackorg/ministack || true | |
| echo "Waiting for MiniStack to be ready..." | |
| MAX_ATTEMPTS=60 | |
| READY=false | |
| for i in $(seq 1 $MAX_ATTEMPTS); do | |
| if ! docker ps | grep -q localstack-main; then | |
| echo "MiniStack container not running (attempt $i/$MAX_ATTEMPTS)" | |
| sleep 2 | |
| continue | |
| fi | |
| if curl -sf http://localhost:4566 > /dev/null 2>&1; then | |
| echo "MiniStack is ready (attempt $i/$MAX_ATTEMPTS)" | |
| READY=true | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| if [ "$READY" != "true" ]; then | |
| echo "ERROR: MiniStack did not become ready" | |
| docker logs localstack-main --tail 100 || true | |
| exit 1 | |
| fi | |
| - name: Install AWS CLI tools | |
| run: | | |
| if ! command -v aws > /dev/null 2>&1; then | |
| pip install awscli || true | |
| fi | |
| - name: Create S3 bucket for tests | |
| run: | | |
| for _ in {1..10}; do | |
| if curl -sf http://localhost:4566 > /dev/null 2>&1; then break; fi | |
| sleep 1 | |
| done | |
| MAX_RETRIES=5 | |
| RETRY_COUNT=0 | |
| BUCKET_CREATED=false | |
| while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ "$BUCKET_CREATED" != "true" ]; do | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| if command -v aws > /dev/null 2>&1; then | |
| if aws --endpoint-url=http://localhost:4566 s3 mb s3://"$AWS_STACK_NAME" 2>&1; then BUCKET_CREATED=true; else sleep 2; fi | |
| else | |
| echo "aws CLI not available"; exit 1 | |
| fi | |
| done | |
| if [ "$BUCKET_CREATED" != "true" ]; then | |
| echo "ERROR: Failed to create S3 bucket" | |
| docker logs localstack-main --tail 50 || true | |
| exit 1 | |
| fi | |
| - run: yarn install --immutable | |
| - run: yarn build | |
| # --- AWS Test 1: orchestrator-image --- | |
| - name: Run orchestrator-image test (AWS) | |
| timeout-minutes: 10 | |
| run: yarn vitest run "orchestrator-image" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: aws | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-image (AWS) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- AWS Test 2: orchestrator-environment --- | |
| - name: Run orchestrator-environment test (AWS) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-environment" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: aws | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-environment (AWS) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- AWS Test 3: orchestrator-s3-steps --- | |
| - name: Run orchestrator-s3-steps test (AWS) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-s3-steps" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: aws | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-s3-steps (AWS) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- AWS Test 4: orchestrator-hooks --- | |
| - name: Run orchestrator-hooks test (AWS) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-hooks" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: aws | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-hooks (AWS) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- AWS Test 5: orchestrator-end2end-caching --- | |
| - name: Run orchestrator-end2end-caching test (AWS) | |
| timeout-minutes: 60 | |
| run: yarn vitest run "orchestrator-end2end-caching" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: aws | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-end2end-caching (AWS) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- AWS Test 6: orchestrator-end2end-retaining --- | |
| - name: Run orchestrator-end2end-retaining test (AWS) | |
| timeout-minutes: 60 | |
| run: yarn vitest run "orchestrator-end2end-retaining" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: aws | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-end2end-retaining (AWS) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- AWS Test 7: orchestrator-caching --- | |
| - name: Run orchestrator-caching test (AWS) | |
| timeout-minutes: 60 | |
| run: yarn vitest run "orchestrator-caching" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: aws | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-caching (AWS) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- AWS Test 8: orchestrator-locking-core --- | |
| - name: Run orchestrator-locking-core test (AWS) | |
| timeout-minutes: 60 | |
| run: yarn vitest run "orchestrator-locking-core" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: aws | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-locking-core (AWS) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- AWS Test 9: orchestrator-locking-get-locked --- | |
| - name: Run orchestrator-locking-get-locked test (AWS) | |
| timeout-minutes: 60 | |
| run: yarn vitest run "orchestrator-locking-get-locked" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: aws | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-locking-get-locked (AWS) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- AWS Test 10: orchestrator-end2end-locking --- | |
| - name: Run orchestrator-end2end-locking test (AWS) | |
| timeout-minutes: 60 | |
| run: yarn vitest run "orchestrator-end2end-locking" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| KUBE_STORAGE_CLASS: local-path | |
| PROVIDER_STRATEGY: aws | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| # --- Final cleanup --- | |
| - name: Final cleanup | |
| if: always() | |
| run: | | |
| rm -rf ./orchestrator-cache/* || true | |
| docker stop localstack-main 2>/dev/null || true | |
| docker rm localstack-main 2>/dev/null || true | |
| docker system prune -af --volumes || true | |
| df -h | |
| # ============================================================================ | |
| # LOCAL DOCKER TESTS | |
| # ============================================================================ | |
| local-docker-tests: | |
| name: Local Docker Provider Tests | |
| runs-on: ubuntu-latest | |
| env: | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT_URL: http://localhost:4566 | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| steps: | |
| # --- Setup --- | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: false | |
| - name: Install package manager (from package.json) | |
| run: | | |
| corepack enable | |
| corepack install | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Resolve yarn cache folder | |
| id: yarn-config | |
| run: echo "cacheFolder=$(yarn config get cacheFolder)" >> "$GITHUB_OUTPUT" | |
| - name: Restore yarn install cache (node_modules + cacheFolder + install-state) | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| plugins/orchestrator/node_modules | |
| ${{ steps.yarn-config.outputs.cacheFolder }} | |
| plugins/orchestrator/.yarn/install-state.gz | |
| key: yarn-${{ runner.os }}-node-20-${{ hashFiles('plugins/orchestrator/yarn.lock') }} | |
| restore-keys: | | |
| yarn-${{ runner.os }}-node-20- | |
| - name: Define cleanup functions | |
| run: | | |
| cat > /tmp/cleanup-functions.sh << 'CLEANUP_EOF' | |
| light_cleanup() { | |
| echo "--- Light cleanup ---" | |
| rm -rf ./orchestrator-cache/* || true | |
| docker system prune -f || true | |
| df -h | |
| } | |
| heavy_cleanup() { | |
| echo "--- Heavy cleanup ---" | |
| rm -rf ./orchestrator-cache/* || true | |
| docker system prune -af --volumes || true | |
| df -h | |
| } | |
| CLEANUP_EOF | |
| echo "Cleanup functions defined at /tmp/cleanup-functions.sh" | |
| - name: Initial disk space cleanup | |
| run: | | |
| echo "Initial disk space cleanup..." | |
| df -h | |
| docker system prune -af --volumes || true | |
| echo "Disk usage after cleanup:" | |
| df -h | |
| - name: Start MiniStack (for S3-dependent local-docker tests) | |
| run: | | |
| echo "Starting MiniStack for S3-dependent tests..." | |
| docker run -d \ | |
| --name localstack-main \ | |
| -p 4566:4566 \ | |
| -e SERVICES=s3,cloudformation \ | |
| -e DEBUG=0 \ | |
| ministackorg/ministack || true | |
| echo "Waiting for MiniStack to be ready..." | |
| MAX_ATTEMPTS=60 | |
| READY=false | |
| for i in $(seq 1 $MAX_ATTEMPTS); do | |
| if ! docker ps | grep -q localstack-main; then | |
| sleep 2 | |
| continue | |
| fi | |
| if curl -sf http://localhost:4566 > /dev/null 2>&1; then | |
| echo "MiniStack is ready (attempt $i/$MAX_ATTEMPTS)" | |
| READY=true | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| if [ "$READY" != "true" ]; then | |
| echo "ERROR: MiniStack did not become ready" | |
| docker logs localstack-main --tail 100 || true | |
| exit 1 | |
| fi | |
| - name: Install AWS CLI and create S3 bucket | |
| run: | | |
| if ! command -v aws > /dev/null 2>&1; then pip install awscli || true; fi | |
| MAX_RETRIES=5 | |
| RETRY_COUNT=0 | |
| BUCKET_CREATED=false | |
| while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ "$BUCKET_CREATED" != "true" ]; do | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| if command -v aws > /dev/null 2>&1; then | |
| if aws --endpoint-url=http://localhost:4566 s3 mb s3://"$AWS_STACK_NAME" 2>&1; then BUCKET_CREATED=true; else sleep 2; fi | |
| else | |
| echo "aws CLI not available"; exit 1 | |
| fi | |
| done | |
| if [ "$BUCKET_CREATED" != "true" ]; then | |
| echo "ERROR: Failed to create S3 bucket"; exit 1 | |
| fi | |
| - run: yarn install --immutable | |
| - run: yarn build | |
| # --- Docker Test 1: orchestrator-image --- | |
| - name: Run orchestrator-image test (local-docker) | |
| timeout-minutes: 10 | |
| run: yarn vitest run "orchestrator-image" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| PROVIDER_STRATEGY: local-docker | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-image (local-docker) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- Docker Test 2: orchestrator-hooks --- | |
| - name: Run orchestrator-hooks test (local-docker) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-hooks" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| PROVIDER_STRATEGY: local-docker | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-hooks (local-docker) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- Docker Test 3: orchestrator-local-persistence --- | |
| - name: Run orchestrator-local-persistence test (local-docker) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-local-persistence" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| PROVIDER_STRATEGY: local-docker | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-local-persistence (local-docker) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- Docker Test 4: orchestrator-locking-core (with S3) --- | |
| - name: Run orchestrator-locking-core test (local-docker with S3) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-locking-core" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| PROVIDER_STRATEGY: local-docker | |
| AWS_STACK_NAME: game-ci-team-pipelines | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT_URL: http://localhost:4566 | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| INPUT_AWSS3ENDPOINT: http://localhost:4566 | |
| INPUT_AWSENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-locking-core (local-docker) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- Docker Test 5: orchestrator-locking-get-locked (with S3) --- | |
| - name: Run orchestrator-locking-get-locked test (local-docker with S3) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-locking-get-locked" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| PROVIDER_STRATEGY: local-docker | |
| AWS_STACK_NAME: game-ci-team-pipelines | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT_URL: http://localhost:4566 | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| INPUT_AWSS3ENDPOINT: http://localhost:4566 | |
| INPUT_AWSENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-locking-get-locked (local-docker) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- Docker Test 6: orchestrator-caching --- | |
| - name: Run orchestrator-caching test (local-docker) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-caching" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| PROVIDER_STRATEGY: local-docker | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-caching (local-docker) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- Docker Test 7: orchestrator-github-checks --- | |
| - name: Run orchestrator-github-checks test (local-docker) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-github-checks" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneWindows64 | |
| orchestratorTests: true | |
| versioning: None | |
| PROVIDER_STRATEGY: local-docker | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-github-checks (local-docker) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- Docker Test 8: orchestrator-s3-steps (with S3) --- | |
| - name: Run orchestrator-s3-steps test (local-docker with S3) | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-s3-steps" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneLinux64 | |
| orchestratorTests: true | |
| versioning: None | |
| PROVIDER_STRATEGY: local-docker | |
| AWS_STACK_NAME: game-ci-team-pipelines | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT_URL: http://localhost:4566 | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| INPUT_AWSS3ENDPOINT: http://localhost:4566 | |
| INPUT_AWSENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| - name: Cleanup after orchestrator-s3-steps (local-docker) | |
| if: always() | |
| run: | | |
| source /tmp/cleanup-functions.sh | |
| light_cleanup | |
| # --- Docker Test 9: orchestrator-end2end-caching (with S3) --- | |
| - name: Run orchestrator-end2end-caching test (local-docker with S3) | |
| timeout-minutes: 60 | |
| run: yarn vitest run "orchestrator-end2end-caching" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneLinux64 | |
| orchestratorTests: true | |
| versioning: None | |
| PROVIDER_STRATEGY: local-docker | |
| AWS_STACK_NAME: game-ci-team-pipelines | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT_URL: http://localhost:4566 | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| INPUT_AWSS3ENDPOINT: http://localhost:4566 | |
| INPUT_AWSENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| # --- Final cleanup --- | |
| - name: Final cleanup | |
| if: always() | |
| run: | | |
| rm -rf ./orchestrator-cache/* || true | |
| docker stop localstack-main 2>/dev/null || true | |
| docker rm localstack-main 2>/dev/null || true | |
| docker system prune -af --volumes || true | |
| df -h | |
| # ============================================================================ | |
| # RCLONE TESTS | |
| # ============================================================================ | |
| rclone-tests: | |
| name: Rclone Provider Tests | |
| runs-on: ubuntu-latest | |
| env: | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_ENDPOINT: http://localhost:4566 | |
| AWS_ENDPOINT_URL: http://localhost:4566 | |
| AWS_S3_ENDPOINT: http://localhost:4566 | |
| AWS_S3_FORCE_PATH_STYLE: 'true' | |
| AWS_EC2_METADATA_DISABLED: 'true' | |
| steps: | |
| # --- Setup --- | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: false | |
| - name: Install package manager (from package.json) | |
| run: | | |
| corepack enable | |
| corepack install | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Resolve yarn cache folder | |
| id: yarn-config | |
| run: echo "cacheFolder=$(yarn config get cacheFolder)" >> "$GITHUB_OUTPUT" | |
| - name: Restore yarn install cache (node_modules + cacheFolder + install-state) | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| plugins/orchestrator/node_modules | |
| ${{ steps.yarn-config.outputs.cacheFolder }} | |
| plugins/orchestrator/.yarn/install-state.gz | |
| key: yarn-${{ runner.os }}-node-20-${{ hashFiles('plugins/orchestrator/yarn.lock') }} | |
| restore-keys: | | |
| yarn-${{ runner.os }}-node-20- | |
| - name: Initial disk space cleanup | |
| run: | | |
| echo "Initial disk space cleanup..." | |
| docker system prune -af --volumes || true | |
| df -h | |
| - name: Start MiniStack | |
| run: | | |
| echo "Starting MiniStack for rclone S3 backend..." | |
| docker run -d \ | |
| --name localstack-main \ | |
| -p 4566:4566 \ | |
| -e SERVICES=s3 \ | |
| -e DEBUG=0 \ | |
| ministackorg/ministack || true | |
| echo "Waiting for MiniStack to be ready..." | |
| MAX_ATTEMPTS=60 | |
| READY=false | |
| for i in $(seq 1 $MAX_ATTEMPTS); do | |
| if ! docker ps | grep -q localstack-main; then sleep 2; continue; fi | |
| if curl -sf http://localhost:4566 > /dev/null 2>&1; then | |
| echo "MiniStack is ready (attempt $i/$MAX_ATTEMPTS)" | |
| READY=true | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| if [ "$READY" != "true" ]; then | |
| echo "ERROR: MiniStack did not become ready" | |
| docker logs localstack-main --tail 100 || true | |
| exit 1 | |
| fi | |
| - name: Install AWS CLI and create S3 bucket | |
| run: | | |
| if ! command -v aws > /dev/null 2>&1; then pip install awscli || true; fi | |
| MAX_RETRIES=5 | |
| RETRY_COUNT=0 | |
| BUCKET_CREATED=false | |
| while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ "$BUCKET_CREATED" != "true" ]; do | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| if command -v aws > /dev/null 2>&1; then | |
| if aws --endpoint-url=http://localhost:4566 s3 mb s3://"$AWS_STACK_NAME" 2>&1; then BUCKET_CREATED=true; else sleep 2; fi | |
| else | |
| echo "aws CLI not available"; exit 1 | |
| fi | |
| done | |
| if [ "$BUCKET_CREATED" != "true" ]; then | |
| echo "ERROR: Failed to create S3 bucket"; exit 1 | |
| fi | |
| - name: Install and configure rclone with MiniStack S3 | |
| run: | | |
| echo "Installing rclone..." | |
| curl https://rclone.org/install.sh | sudo bash | |
| rclone version | |
| echo "Configuring rclone to use MiniStack S3..." | |
| mkdir -p ~/.config/rclone | |
| cat > ~/.config/rclone/rclone.conf << 'EOF' | |
| [ministack-s3] | |
| type = s3 | |
| provider = Other | |
| env_auth = false | |
| access_key_id = test | |
| secret_access_key = test | |
| endpoint = http://localhost:4566 | |
| acl = private | |
| force_path_style = true | |
| EOF | |
| echo "Testing rclone configuration..." | |
| rclone lsd ministack-s3: || echo "No buckets yet (expected)" | |
| rclone ls ministack-s3:game-ci-team-pipelines || echo "Bucket may be empty" | |
| echo "Rclone configured successfully" | |
| - run: yarn install --immutable | |
| - run: yarn build | |
| # --- Rclone Test 1: orchestrator-rclone-steps --- | |
| - name: Run orchestrator-rclone-steps test | |
| timeout-minutes: 30 | |
| run: yarn vitest run "orchestrator-rclone-steps" --no-file-parallelism | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| TARGET_PLATFORM: StandaloneLinux64 | |
| orchestratorTests: true | |
| versioning: None | |
| PROVIDER_STRATEGY: local-docker | |
| RCLONE_REMOTE: 'ministack-s3:game-ci-team-pipelines' | |
| rcloneRemote: 'ministack-s3:game-ci-team-pipelines' | |
| GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| GITHUB_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN || github.token }} | |
| # --- Final cleanup --- | |
| - name: Final cleanup | |
| if: always() | |
| run: | | |
| rm -rf ./orchestrator-cache/* || true | |
| docker stop localstack-main 2>/dev/null || true | |
| docker rm localstack-main 2>/dev/null || true | |
| docker system prune -af --volumes || true | |
| df -h | |
| # ============================================================================ | |
| # MOCK AWS TESTS (sibling to MiniStack — no external dependencies) | |
| # ============================================================================ | |
| mock-aws-tests: | |
| name: Mock AWS Provider Tests | |
| runs-on: ubuntu-latest | |
| env: | |
| PROVIDER_STRATEGY: mock-aws | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: false | |
| - name: Read Node version from mise.toml | |
| id: node | |
| run: echo "version=$(grep -E '^node\\s*=' mise.toml | sed -E 's/.*"([^"]+)".*/\\1/')" >> "$GITHUB_OUTPUT" | |
| - name: Install package manager (from package.json) | |
| run: | | |
| corepack enable | |
| corepack install | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ steps.node.outputs.version }} | |
| - name: Resolve yarn cache folder | |
| id: yarn-config | |
| run: echo "cacheFolder=$(yarn config get cacheFolder)" >> "$GITHUB_OUTPUT" | |
| - name: Restore yarn install cache (node_modules + cacheFolder + install-state) | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| plugins/orchestrator/node_modules | |
| ${{ steps.yarn-config.outputs.cacheFolder }} | |
| plugins/orchestrator/.yarn/install-state.gz | |
| key: yarn-${{ runner.os }}-node${{ steps.node.outputs.version }}-${{ hashFiles('plugins/orchestrator/yarn.lock') }} | |
| restore-keys: | | |
| yarn-${{ runner.os }}-node${{ steps.node.outputs.version }}- | |
| - name: Install deps | |
| env: | |
| YARN_ENABLE_HARDENED_MODE: 'false' | |
| run: | | |
| case "$(yarn --version)" in 1.*) echo 'expected up-to-date yarn version'; exit 1 ;; esac | |
| yarn install --immutable | |
| - run: yarn build | |
| # --- Mock AWS unit tests (CloudFormation, ECS, S3, Kinesis, CloudWatch) --- | |
| - name: Run mock AWS service tests | |
| run: yarn vitest run src/model/orchestrator/providers/mock-aws/mock-aws.test.ts --coverage=false | |
| # --- Mock AWS provider integration (exercises full ProviderInterface) --- | |
| - name: Validate mock-aws provider loads | |
| run: | | |
| node -e " | |
| const loadProvider = require('./dist/model/orchestrator/providers/provider-loader').default; | |
| const BuildParameters = require('./dist/model/build-parameters').default; | |
| const bp = new BuildParameters(); | |
| bp.awsStackName = 'mock-test-stack'; | |
| loadProvider('mock-aws', bp).then(provider => { | |
| console.log('Mock AWS provider loaded successfully'); | |
| console.log('Methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(provider)).filter(m => m !== 'constructor')); | |
| }).catch(err => { | |
| console.error('Failed:', err.message); | |
| process.exit(1); | |
| }); | |
| " | |
| # --- Test full workflow: setup → run task → cleanup --- | |
| - name: Test mock-aws full workflow | |
| run: | | |
| node -e " | |
| const loadProvider = require('./dist/model/orchestrator/providers/provider-loader').default; | |
| const BuildParameters = require('./dist/model/build-parameters').default; | |
| async function main() { | |
| const bp = new BuildParameters(); | |
| bp.awsStackName = 'game-ci-mock-integration'; | |
| const provider = await loadProvider('mock-aws', bp); | |
| // Setup | |
| await provider.setupWorkflow('test-guid', bp, 'main', []); | |
| console.log('setupWorkflow: OK'); | |
| // Run task | |
| const output = await provider.runTaskInWorkflow( | |
| 'test-guid', | |
| 'barichello/godot-ci:4.3', | |
| 'echo hello from mock', | |
| '/build', | |
| '/build', | |
| [{ name: 'CI', value: 'true' }], | |
| [] | |
| ); | |
| console.log('runTaskInWorkflow output:', output.substring(0, 200)); | |
| // List resources | |
| const resources = await provider.listResources(); | |
| console.log('listResources:', resources.length, 'resources'); | |
| // Garbage collect | |
| const gcResult = await provider.garbageCollect('', false, 0, false, false); | |
| console.log('garbageCollect:', gcResult); | |
| // Cleanup | |
| await provider.cleanupWorkflow(bp, 'main', []); | |
| console.log('cleanupWorkflow: OK'); | |
| console.log('=== Mock AWS full workflow test PASSED ==='); | |
| } | |
| main().catch(err => { console.error(err); process.exit(1); }); | |
| " |