"""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 = [ # Scenario 1: Registry mismatch — build tags ghcr.io but push targets docker.io { "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/:\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", } ], }, # Scenario 2: Image tag mismatch between build and push steps { "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:\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", } ], }, # Scenario 3: Build and push use different tagging strategies (sha vs latest) { "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:)\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", } ], }, # Scenario 4: Dockerfile ARG not passed from workflow build-arg { "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", } ], }, # Scenario 5: Dockerfile path wrong in workflow when using subdirectory structure { "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", } ], }, ]