| """Task: Full Stack Deployment Pipeline — EXPERT. |
| |
| Agent debugs multi-error scenarios spanning the entire stack: |
| GHA workflow + Dockerfile + Kubernetes manifests. |
| Multiple bugs per scenario requiring cross-file reasoning. |
| """ |
|
|
| from server.models import TaskDifficulty |
| from server.tasks.base import BaseTask |
|
|
|
|
| class PipelineFullTask(BaseTask): |
| NAME = "Full Stack Deployment Pipeline" |
| DESCRIPTION = "Debug complex multi-error deployment pipelines across GHA workflows, Dockerfiles, and Kubernetes manifests" |
| DIFFICULTY = TaskDifficulty.HARD |
| AVAILABLE_SECRETS = ["GITHUB_TOKEN", "DOCKER_USERNAME", "DOCKER_PASSWORD"] |
|
|
| SCENARIOS = [ |
| |
| { |
| "id": "full_pipeline_ghcr_and_selector", |
| "files": [ |
| { |
| "path": ".github/workflows/deploy.yml", |
| "type": "workflow", |
| "content": ( |
| "name: Build and Deploy\n" |
| "on:\n" |
| " push:\n" |
| " branches: [main]\n" |
| "\n" |
| "jobs:\n" |
| " deploy:\n" |
| " runs-on: ubuntu-latest\n" |
| " steps:\n" |
| " - uses: actions/checkout@v4\n" |
| "\n" |
| " - name: Build Docker image\n" |
| " run: docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .\n" |
| "\n" |
| " - name: Login to GHCR\n" |
| " run: echo $GITHUB_TOKEN | docker login ghcr.io -u ${{ github.actor }} --password-stdin\n" |
| "\n" |
| " - name: Push image\n" |
| " run: docker push ghcr.io/${{ github.repository }}:${{ github.sha }}\n" |
| ), |
| }, |
| { |
| "path": "Dockerfile", |
| "type": "dockerfile", |
| "content": ( |
| "FROM node:20-alpine\n" |
| "WORKDIR /app\n" |
| "COPY package*.json ./\n" |
| "RUN npm ci\n" |
| "COPY . .\n" |
| "EXPOSE 3000\n" |
| 'CMD ["npm", "start"]\n' |
| ), |
| }, |
| { |
| "path": "package.json", |
| "type": "other", |
| "content": '{"name": "myapp", "scripts": {"start": "node server.js"}}', |
| }, |
| { |
| "path": "k8s/deployment.yaml", |
| "type": "kubernetes", |
| "content": ( |
| "apiVersion: apps/v1\n" |
| "kind: Deployment\n" |
| "metadata:\n" |
| " name: myapp\n" |
| "spec:\n" |
| " replicas: 3\n" |
| " selector:\n" |
| " matchLabels:\n" |
| " app: myapp\n" |
| " template:\n" |
| " metadata:\n" |
| " labels:\n" |
| " app: myapp\n" |
| " spec:\n" |
| " containers:\n" |
| " - name: app\n" |
| " image: ghcr.io/OWNER/REPO:TAG\n" |
| " ports:\n" |
| " - containerPort: 3000\n" |
| ), |
| }, |
| { |
| "path": "k8s/service.yaml", |
| "type": "kubernetes", |
| "content": ( |
| "apiVersion: v1\n" |
| "kind: Service\n" |
| "metadata:\n" |
| " name: myapp-service\n" |
| "spec:\n" |
| " selector:\n" |
| " app: my-app\n" |
| " ports:\n" |
| " - port: 80\n" |
| " targetPort: 3000\n" |
| ), |
| }, |
| ], |
| "error": { |
| "phase": "pipeline_deploy", |
| "message": ( |
| "Run: Build and Deploy\n" |
| "\n" |
| "Step: Login to GHCR ✗\n" |
| "Error: Cannot perform an interactive login from a non TTY device\n" |
| "Error: GITHUB_TOKEN environment variable is not set\n" |
| "\n" |
| "---\n" |
| "(If login had succeeded, deployment would also fail with:)\n" |
| "Error: Service 'myapp-service' has no endpoints" |
| ), |
| }, |
| "expected_fixes": [ |
| { |
| "file": ".github/workflows/deploy.yml", |
| "type": "contains", |
| "expected": "GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}", |
| "hint": "GITHUB_TOKEN is used as shell variable but not mapped from secrets via env block", |
| }, |
| { |
| "file": "k8s/service.yaml", |
| "type": "contains", |
| "expected": "app: myapp", |
| "hint": "Service selector 'app: my-app' doesn't match Deployment label 'app: myapp'", |
| }, |
| ], |
| }, |
|
|
| |
| { |
| "id": "full_pipeline_three_bugs", |
| "files": [ |
| { |
| "path": ".github/workflows/ci.yml", |
| "type": "workflow", |
| "content": ( |
| "name: CI Pipeline\n" |
| "on:\n" |
| " push:\n" |
| " branches: [main]\n" |
| "\n" |
| "jobs:\n" |
| " build:\n" |
| " runs-on: ubuntu-latest\n" |
| " steps:\n" |
| " - name: Build image\n" |
| " run: docker build -t myapp:${{ github.sha }} .\n" |
| "\n" |
| " - name: Run tests\n" |
| " run: docker run myapp:${{ github.sha }} npm test\n" |
| ), |
| }, |
| { |
| "path": "Dockerfile", |
| "type": "dockerfile", |
| "content": ( |
| "FROM node:18-alpine\n" |
| "COPY package*.json ./\n" |
| "RUN npm ci\n" |
| "COPY . .\n" |
| "EXPOSE 3000\n" |
| 'CMD ["npm", "start"]\n' |
| ), |
| }, |
| { |
| "path": "package.json", |
| "type": "other", |
| "content": '{"name": "myapp", "scripts": {"start": "node server.js", "test": "jest"}}', |
| }, |
| { |
| "path": "k8s/deployment.yaml", |
| "type": "kubernetes", |
| "content": ( |
| "apiVersion: apps/v1\n" |
| "kind: Deployment\n" |
| "metadata:\n" |
| " name: myapp\n" |
| "spec:\n" |
| " replicas: 2\n" |
| " selector:\n" |
| " matchLabels:\n" |
| " app: myapp\n" |
| " template:\n" |
| " metadata:\n" |
| " labels:\n" |
| " app: myapp\n" |
| " spec:\n" |
| " containers:\n" |
| " - name: app\n" |
| " image: myapp:latest\n" |
| " ports:\n" |
| " - containerPort: 8080\n" |
| ), |
| }, |
| { |
| "path": "k8s/service.yaml", |
| "type": "kubernetes", |
| "content": ( |
| "apiVersion: v1\n" |
| "kind: Service\n" |
| "metadata:\n" |
| " name: myapp-svc\n" |
| "spec:\n" |
| " selector:\n" |
| " app: myapp\n" |
| " ports:\n" |
| " - port: 80\n" |
| " targetPort: 8080\n" |
| ), |
| }, |
| ], |
| "error": { |
| "phase": "pipeline_deploy", |
| "message": ( |
| "Run: CI Pipeline\n" |
| "\n" |
| "Step: Build image ✗\n" |
| "Error: Checkout must happen before Docker build steps\n" |
| "(No actions/checkout@v4 step found before docker build)\n" |
| "\n" |
| "---\n" |
| "Additionally:\n" |
| "- Dockerfile: npm reports module resolution errors at runtime\n" |
| "- K8s: Service returns connection refused when accessed" |
| ), |
| }, |
| "expected_fixes": [ |
| { |
| "file": ".github/workflows/ci.yml", |
| "type": "contains", |
| "expected": "actions/checkout@v4", |
| "hint": "Workflow needs a checkout step before docker build", |
| }, |
| { |
| "file": "Dockerfile", |
| "type": "contains", |
| "expected": "WORKDIR /app", |
| "hint": "Dockerfile needs WORKDIR /app before COPY commands", |
| }, |
| { |
| "file": "k8s/deployment.yaml", |
| "type": "contains", |
| "expected": "containerPort: 3000", |
| "hint": "Container port should be 3000 to match the app's EXPOSE/listen port", |
| }, |
| { |
| "file": "k8s/service.yaml", |
| "type": "contains", |
| "expected": "targetPort: 3000", |
| "hint": "Service targetPort should be 3000 to match container port", |
| }, |
| ], |
| }, |
|
|
| |
| { |
| "id": "full_pipeline_ghcr_dockerfile_k8s", |
| "files": [ |
| { |
| "path": ".github/workflows/release.yml", |
| "type": "workflow", |
| "content": ( |
| "name: Release Pipeline\n" |
| "on:\n" |
| " release:\n" |
| " types: [published]\n" |
| "\n" |
| "jobs:\n" |
| " release:\n" |
| " runs-on: ubuntu-latest\n" |
| " steps:\n" |
| " - uses: actions/checkout@v4\n" |
| "\n" |
| " - name: Login to GHCR\n" |
| " run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin\n" |
| "\n" |
| " - name: Build\n" |
| " run: docker build -t ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} .\n" |
| "\n" |
| " - name: Push\n" |
| " run: docker push ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }}\n" |
| ), |
| }, |
| { |
| "path": "Dockerfile", |
| "type": "dockerfile", |
| "content": ( |
| "FROM python:3.9-slimm\n" |
| "WORKDIR /app\n" |
| "COPY requirements.txt .\n" |
| "RUN pip install -r requirements.txt\n" |
| "COPY . .\n" |
| "EXPOSE 7860\n" |
| 'CMD ["gunicorn", "app:app", "-b", "0.0.0.0:7860"]\n' |
| ), |
| }, |
| { |
| "path": "requirements.txt", |
| "type": "requirements", |
| "content": "flask==3.0.0\ngunicorn==21.2.0\n", |
| }, |
| { |
| "path": "k8s/deployment.yaml", |
| "type": "kubernetes", |
| "content": ( |
| "apiVersion: apps/v1\n" |
| "kind: Deployment\n" |
| "metadata:\n" |
| " name: api\n" |
| "spec:\n" |
| " replicas: 3\n" |
| " selector:\n" |
| " matchLabels:\n" |
| " app: api\n" |
| " template:\n" |
| " metadata:\n" |
| " labels:\n" |
| " app: api\n" |
| " spec:\n" |
| " containers:\n" |
| " - name: api\n" |
| " image: ghcr.io/myorg/myapp:latest\n" |
| " ports:\n" |
| " - containerPort: 7860\n" |
| " resources:\n" |
| " limits:\n" |
| ' memory: "64Mi"\n' |
| ' cpu: "100m"\n' |
| ), |
| }, |
| ], |
| "error": { |
| "phase": "pipeline_deploy", |
| "message": ( |
| "Run: Release Pipeline\n" |
| "\n" |
| "Step: Login to GHCR ✗\n" |
| "Error: GHCR requires GITHUB_TOKEN for authentication, not DOCKER_PASSWORD\n" |
| "\n" |
| "---\n" |
| "Additional issues found:\n" |
| "- Dockerfile: pull access denied for base image — repository does not exist\n" |
| "- K8s: Pod in CrashLoopBackOff with exit code 137" |
| ), |
| }, |
| "expected_fixes": [ |
| { |
| "file": ".github/workflows/release.yml", |
| "type": "contains", |
| "expected": "secrets.GITHUB_TOKEN", |
| "hint": "GHCR uses GITHUB_TOKEN, not DOCKER_PASSWORD", |
| }, |
| { |
| "file": "Dockerfile", |
| "type": "not_contains", |
| "expected": "python:3.9-slimm", |
| "hint": "Base image tag has a typo: 'slimm' should be 'slim'", |
| }, |
| { |
| "file": "k8s/deployment.yaml", |
| "type": "contains", |
| "expected": 'memory: "256Mi"', |
| "hint": "Memory limit 64Mi is too low for gunicorn — increase to at least 256Mi", |
| }, |
| ], |
| }, |
|
|
| |
| { |
| "id": "full_pipeline_permissions_image_ingress", |
| "files": [ |
| { |
| "path": ".github/workflows/deploy.yml", |
| "type": "workflow", |
| "content": ( |
| "name: Deploy to Production\n" |
| "on:\n" |
| " push:\n" |
| " branches: [main]\n" |
| "\n" |
| "jobs:\n" |
| " build-and-push:\n" |
| " runs-on: ubuntu-latest\n" |
| " steps:\n" |
| " - uses: actions/checkout@v4\n" |
| "\n" |
| " - name: Login to GHCR\n" |
| " run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin\n" |
| "\n" |
| " - name: Build and push\n" |
| " run: |\n" |
| " docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .\n" |
| " docker push ghcr.io/${{ github.repository }}:${{ github.sha }}\n" |
| ), |
| }, |
| { |
| "path": "Dockerfile", |
| "type": "dockerfile", |
| "content": ( |
| "FROM node:20-alpine\n" |
| "WORKDIR /app\n" |
| "COPY package*.json ./\n" |
| "RUN npm ci\n" |
| "COPY . .\n" |
| "EXPOSE 3000\n" |
| 'CMD ["npm", "start"]\n' |
| ), |
| }, |
| { |
| "path": "package.json", |
| "type": "other", |
| "content": '{"name": "app", "scripts": {"start": "node index.js"}}', |
| }, |
| { |
| "path": "k8s/deployment.yaml", |
| "type": "kubernetes", |
| "content": ( |
| "apiVersion: apps/v1\n" |
| "kind: Deployment\n" |
| "metadata:\n" |
| " name: webapp\n" |
| "spec:\n" |
| " replicas: 3\n" |
| " selector:\n" |
| " matchLabels:\n" |
| " app: webapp\n" |
| " template:\n" |
| " metadata:\n" |
| " labels:\n" |
| " app: webapp\n" |
| " spec:\n" |
| " containers:\n" |
| " - name: webapp\n" |
| " image: ghcr.io/OWNER/REPO:TAG\n" |
| " ports:\n" |
| " - containerPort: 3000\n" |
| ), |
| }, |
| { |
| "path": "k8s/service.yaml", |
| "type": "kubernetes", |
| "content": ( |
| "apiVersion: v1\n" |
| "kind: Service\n" |
| "metadata:\n" |
| " name: webapp-svc\n" |
| "spec:\n" |
| " selector:\n" |
| " app: webapp\n" |
| " ports:\n" |
| " - port: 80\n" |
| " targetPort: 3000\n" |
| ), |
| }, |
| { |
| "path": "k8s/ingress.yaml", |
| "type": "kubernetes", |
| "content": ( |
| "apiVersion: networking.k8s.io/v1\n" |
| "kind: Ingress\n" |
| "metadata:\n" |
| " name: webapp-ingress\n" |
| "spec:\n" |
| " rules:\n" |
| " - host: webapp.example.com\n" |
| " http:\n" |
| " paths:\n" |
| " - path: /\n" |
| " pathType: Prefix\n" |
| " backend:\n" |
| " service:\n" |
| " name: webapp-svc\n" |
| " port:\n" |
| " number: 80\n" |
| ), |
| }, |
| ], |
| "error": { |
| "phase": "pipeline_deploy", |
| "message": ( |
| "Run: Deploy to Production\n" |
| "\n" |
| "Step: Build and push ✗\n" |
| "Error: denied: permission_denied: write_package\n" |
| "GITHUB_TOKEN does not have packages:write permission\n" |
| "\n" |
| "---\n" |
| "Additional issues:\n" |
| "- K8s Deployment image is hardcoded as 'ghcr.io/OWNER/REPO:TAG' — " |
| "should reference the actual built image\n" |
| "- Ingress has no ingressClassName — won't be picked up by nginx controller" |
| ), |
| }, |
| "expected_fixes": [ |
| { |
| "file": ".github/workflows/deploy.yml", |
| "type": "contains", |
| "expected": "packages: write", |
| "hint": "Add permissions block with 'packages: write' to allow GHCR push", |
| }, |
| { |
| "file": "k8s/deployment.yaml", |
| "type": "not_contains", |
| "expected": "OWNER/REPO:TAG", |
| "hint": "Replace hardcoded 'OWNER/REPO:TAG' placeholder with actual image reference", |
| }, |
| { |
| "file": "k8s/ingress.yaml", |
| "type": "contains", |
| "expected": "ingressClassName: nginx", |
| "hint": "Add ingressClassName: nginx to the Ingress spec", |
| }, |
| ], |
| }, |
|
|
| |
| { |
| "id": "full_pipeline_secrets_build_probe", |
| "files": [ |
| { |
| "path": ".github/workflows/build.yml", |
| "type": "workflow", |
| "content": ( |
| "name: Build and Push\n" |
| "on:\n" |
| " push:\n" |
| " branches: [main]\n" |
| "\n" |
| "jobs:\n" |
| " build:\n" |
| " runs-on: ubuntu-latest\n" |
| " steps:\n" |
| " - uses: actions/checkout@v4\n" |
| "\n" |
| " - name: Login to DockerHub\n" |
| " run: echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin\n" |
| "\n" |
| " - name: Build\n" |
| " run: docker build -t myuser/frontend:${{ github.sha }} .\n" |
| "\n" |
| " - name: Push\n" |
| " run: docker push myuser/frontend:${{ github.sha }}\n" |
| ), |
| }, |
| { |
| "path": "Dockerfile", |
| "type": "dockerfile", |
| "content": ( |
| "FROM node:20-alpine AS builder\n" |
| "WORKDIR /app\n" |
| "COPY package*.json ./\n" |
| "RUN npm ci\n" |
| "COPY . .\n" |
| "RUN npm run build\n" |
| "\n" |
| "FROM nginx:alpine\n" |
| "COPY --from=builder /app/dist /usr/share/nginx/html\n" |
| "EXPOSE 80\n" |
| 'CMD ["nginx", "-g", "daemon off;"]\n' |
| ), |
| }, |
| { |
| "path": "package.json", |
| "type": "other", |
| "content": '{"name": "frontend", "scripts": {"build": "react-scripts build", "start": "react-scripts start"}}', |
| }, |
| { |
| "path": "k8s/deployment.yaml", |
| "type": "kubernetes", |
| "content": ( |
| "apiVersion: apps/v1\n" |
| "kind: Deployment\n" |
| "metadata:\n" |
| " name: frontend\n" |
| "spec:\n" |
| " replicas: 2\n" |
| " selector:\n" |
| " matchLabels:\n" |
| " app: frontend\n" |
| " template:\n" |
| " metadata:\n" |
| " labels:\n" |
| " app: frontend\n" |
| " spec:\n" |
| " containers:\n" |
| " - name: frontend\n" |
| " image: myuser/frontend:latest\n" |
| " ports:\n" |
| " - containerPort: 80\n" |
| " livenessProbe:\n" |
| " httpGet:\n" |
| " path: /healthz\n" |
| " port: 3000\n" |
| " initialDelaySeconds: 10\n" |
| " periodSeconds: 5\n" |
| ), |
| }, |
| ], |
| "error": { |
| "phase": "pipeline_deploy", |
| "message": ( |
| "Run: Build and Push\n" |
| "\n" |
| "Step: Login to DockerHub ✗\n" |
| "Error: DOCKER_USERNAME and DOCKER_PASSWORD env vars are empty — " |
| "secrets not wired via env block\n" |
| "\n" |
| "---\n" |
| "Additional issues:\n" |
| "- Dockerfile: COPY failed: stat app/dist: file does not exist " |
| "(react-scripts outputs to 'build/' not 'dist/')\n" |
| "- K8s: Liveness probe port 3000 doesn't match container port 80 " |
| "(nginx listens on 80)" |
| ), |
| }, |
| "expected_fixes": [ |
| { |
| "file": ".github/workflows/build.yml", |
| "type": "contains", |
| "expected": "DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}", |
| "hint": "Docker login secrets need to be mapped via env block", |
| }, |
| { |
| "file": ".github/workflows/build.yml", |
| "type": "contains", |
| "expected": "DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}", |
| "hint": "Both DOCKER_USERNAME and DOCKER_PASSWORD must be in env block", |
| }, |
| { |
| "file": "Dockerfile", |
| "type": "contains", |
| "expected": "COPY --from=builder /app/build", |
| "hint": "react-scripts outputs to 'build/' not 'dist/'", |
| }, |
| { |
| "file": "k8s/deployment.yaml", |
| "type": "contains", |
| "expected": "port: 80", |
| "hint": "Liveness probe port should be 80 to match nginx container", |
| }, |
| ], |
| }, |
| ] |
|
|