| """Task: CI/CD Build & Push Pipeline β HARD. |
| |
| Agent debugs combined GHA + Docker + Registry pipeline failures: |
| GHCR login missing token, wrong image tag in workflow, missing permissions, |
| Dockerfile + workflow arg mismatch, multi-stage build output mismatch. |
| """ |
|
|
| from server.models import TaskDifficulty |
| from server.tasks.base import BaseTask |
|
|
|
|
| class PipelineBuildDeployTask(BaseTask): |
| NAME = "CI/CD Build & Push Pipeline" |
| DESCRIPTION = "Debug GHA-to-Docker-to-Registry pipeline failures across multiple files" |
| DIFFICULTY = TaskDifficulty.HARD |
| AVAILABLE_SECRETS = ["GITHUB_TOKEN", "DOCKER_USERNAME", "DOCKER_PASSWORD"] |
|
|
| SCENARIOS = [ |
| |
| { |
| "id": "registry_mismatch", |
| "files": [ |
| { |
| "path": ".github/workflows/deploy.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" |
| " permissions:\n" |
| " packages: write\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 image\n" |
| " run: docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .\n" |
| "\n" |
| " - name: Push image\n" |
| " run: docker push docker.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"}}', |
| }, |
| ], |
| "error": { |
| "phase": "pipeline_build", |
| "message": ( |
| "Run: Build and Push\n" |
| "\n" |
| "Step: Build image β\n" |
| "Step: Push image β\n" |
| "Error: An image does not exist locally with the tag: docker.io/<repo>:<sha>\n" |
| "\n" |
| "The image was built with a ghcr.io tag but the push targets docker.io." |
| ), |
| "exit_code": 1, |
| "failed_step": "Push image", |
| }, |
| "expected_fixes": [ |
| { |
| "file": ".github/workflows/deploy.yml", |
| "type": "contains", |
| "expected": "docker push ghcr.io/", |
| "hint": "The push command targets docker.io but the image was tagged with ghcr.io β use the same registry", |
| } |
| ], |
| }, |
|
|
| |
| { |
| "id": "image_tag_mismatch", |
| "files": [ |
| { |
| "path": ".github/workflows/build.yml", |
| "type": "workflow", |
| "content": ( |
| "name: Build and Push\n" |
| "on:\n" |
| " push:\n" |
| " tags: ['v*']\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 ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin\n" |
| "\n" |
| " - name: Build image\n" |
| " run: docker build -t myuser/myapp:${{ github.ref_name }} .\n" |
| "\n" |
| " - name: Push image\n" |
| " run: docker push myuser/myapp:${{ github.sha }}\n" |
| ), |
| }, |
| { |
| "path": "Dockerfile", |
| "type": "dockerfile", |
| "content": ( |
| "FROM python:3.11-slim\n" |
| "WORKDIR /app\n" |
| "COPY requirements.txt .\n" |
| "RUN pip install -r requirements.txt\n" |
| "COPY . .\n" |
| "EXPOSE 7860\n" |
| 'CMD ["python", "app.py"]\n' |
| ), |
| }, |
| { |
| "path": "requirements.txt", |
| "type": "requirements", |
| "content": "flask==3.0.0\ngunicorn==21.2.0\n", |
| }, |
| ], |
| "error": { |
| "phase": "pipeline_build", |
| "message": ( |
| "Run: Build and Push\n" |
| "\n" |
| "Step: Build image β\n" |
| "Step: Push image β\n" |
| "Error: An image does not exist locally with the tag: myuser/myapp:<sha>\n" |
| "\n" |
| "The build used github.ref_name as the tag but push used github.sha. " |
| "These are different values." |
| ), |
| "exit_code": 1, |
| "failed_step": "Push image", |
| }, |
| "expected_fixes": [ |
| { |
| "file": ".github/workflows/build.yml", |
| "type": "contains", |
| "expected": "docker push myuser/myapp:${{ github.ref_name }}", |
| "hint": "Build tags image with github.ref_name but push uses github.sha β use the same tag", |
| } |
| ], |
| }, |
|
|
| |
| { |
| "id": "inconsistent_tagging", |
| "files": [ |
| { |
| "path": ".github/workflows/publish.yml", |
| "type": "workflow", |
| "content": ( |
| "name: Publish\n" |
| "on:\n" |
| " push:\n" |
| " branches: [main]\n" |
| "\n" |
| "jobs:\n" |
| " publish:\n" |
| " runs-on: ubuntu-latest\n" |
| " steps:\n" |
| " - uses: actions/checkout@v4\n" |
| "\n" |
| " - name: Login to DockerHub\n" |
| " run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin\n" |
| "\n" |
| " - name: Build\n" |
| " run: docker build -t myuser/api:${{ github.sha }} .\n" |
| "\n" |
| " - name: Test\n" |
| " run: docker run myuser/api:${{ github.sha }} python -m pytest\n" |
| "\n" |
| " - name: Tag latest\n" |
| " run: docker tag myuser/api:latest myuser/api:stable\n" |
| "\n" |
| " - name: Push\n" |
| " run: |\n" |
| " docker push myuser/api:${{ github.sha }}\n" |
| " docker push myuser/api:stable\n" |
| ), |
| }, |
| { |
| "path": "Dockerfile", |
| "type": "dockerfile", |
| "content": ( |
| "FROM python:3.11-slim\n" |
| "WORKDIR /app\n" |
| "COPY requirements.txt .\n" |
| "RUN pip install -r requirements.txt\n" |
| "COPY . .\n" |
| 'CMD ["python", "app.py"]\n' |
| ), |
| }, |
| { |
| "path": "requirements.txt", |
| "type": "requirements", |
| "content": "flask==3.0.0\npytest==7.4.0\n", |
| }, |
| ], |
| "error": { |
| "phase": "pipeline_build", |
| "message": ( |
| "Run: Publish\n" |
| "\n" |
| "Step: Build β (myuser/api:<sha>)\n" |
| "Step: Test β\n" |
| "Step: Tag latest β\n" |
| "Error: No such image: myuser/api:latest\n" |
| "\n" |
| "The tag command references 'myuser/api:latest' but no image with that tag exists." |
| ), |
| "exit_code": 1, |
| "failed_step": "Tag latest", |
| }, |
| "expected_fixes": [ |
| { |
| "file": ".github/workflows/publish.yml", |
| "type": "contains", |
| "expected": "docker tag myuser/api:${{ github.sha }}", |
| "hint": "The 'docker tag' source must match the tag used in the build step β use the sha-tagged image as source", |
| } |
| ], |
| }, |
|
|
| |
| { |
| "id": "build_arg_not_passed", |
| "files": [ |
| { |
| "path": ".github/workflows/build.yml", |
| "type": "workflow", |
| "content": ( |
| "name: Build with Version\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: Build image\n" |
| " run: docker build -t myapp:${{ github.sha }} .\n" |
| ), |
| }, |
| { |
| "path": "Dockerfile", |
| "type": "dockerfile", |
| "content": ( |
| "FROM python:3.11-slim\n" |
| "ARG APP_VERSION\n" |
| "WORKDIR /app\n" |
| "COPY . .\n" |
| "RUN echo $APP_VERSION > /app/version.txt\n" |
| "EXPOSE 7860\n" |
| 'CMD ["python", "app.py"]\n' |
| ), |
| }, |
| ], |
| "error": { |
| "phase": "pipeline_build", |
| "message": ( |
| "Run: Build with Version\n" |
| "\n" |
| "Step: Build image β (with warnings)\n" |
| "Warning: /app/version.txt is empty β APP_VERSION build arg was not provided\n" |
| "\n" |
| "The Dockerfile declares ARG APP_VERSION but the docker build command " |
| "does not pass --build-arg APP_VERSION=..." |
| ), |
| "exit_code": 0, |
| "failed_step": "Build image", |
| }, |
| "expected_fixes": [ |
| { |
| "file": ".github/workflows/build.yml", |
| "type": "contains", |
| "expected": "--build-arg APP_VERSION=", |
| "hint": "Dockerfile uses ARG APP_VERSION but the build command doesn't pass --build-arg", |
| } |
| ], |
| }, |
|
|
| |
| { |
| "id": "dockerfile_path_in_subdirectory", |
| "files": [ |
| { |
| "path": ".github/workflows/build.yml", |
| "type": "workflow", |
| "content": ( |
| "name: Build API\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: Build API image\n" |
| " uses: docker/build-push-action@v5\n" |
| " with:\n" |
| " context: ./services/api\n" |
| " file: ./Dockerfile\n" |
| " push: false\n" |
| " tags: api:latest\n" |
| ), |
| }, |
| { |
| "path": "services/api/Dockerfile", |
| "type": "dockerfile", |
| "content": ( |
| "FROM python:3.11-slim\n" |
| "WORKDIR /app\n" |
| "COPY requirements.txt .\n" |
| "RUN pip install -r requirements.txt\n" |
| "COPY . .\n" |
| "EXPOSE 7860\n" |
| 'CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]\n' |
| ), |
| }, |
| { |
| "path": "services/api/requirements.txt", |
| "type": "requirements", |
| "content": "fastapi==0.104.0\nuvicorn==0.24.0\n", |
| }, |
| ], |
| "error": { |
| "phase": "pipeline_build", |
| "message": ( |
| "Run: Build API\n" |
| "\n" |
| "Step: Build API image β\n" |
| "Error: unable to prepare context: unable to evaluate symlinks in Dockerfile path: " |
| "lstat /home/runner/work/repo/repo/Dockerfile: no such file or directory\n" |
| "\n" |
| "The Dockerfile is not at the repository root." |
| ), |
| "exit_code": 1, |
| "failed_step": "Build API image", |
| }, |
| "expected_fixes": [ |
| { |
| "file": ".github/workflows/build.yml", |
| "type": "contains", |
| "expected": "file: ./services/api/Dockerfile", |
| "hint": "The 'file' path must point to where the Dockerfile actually is β ./services/api/Dockerfile, not ./Dockerfile", |
| } |
| ], |
| }, |
| ] |
|
|