Files
plex-playlist/Dockerfile.cicd
copilotcoder f14a1c3c20
All checks were successful
CICD Start / Sanity and Base Decision (pull_request) Successful in 1m8s
I think we almost have it finally.
Signed-off-by: copilotcoder <copilotcoder@darkhelm.org>
2026-06-16 12:22:52 -04:00

301 lines
14 KiB
Docker
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CICD Complete Setup - Optimized Build Order for Maximum Caching
# OPTIMIZATION STRATEGY:
# Phase 1: Extract dependency files (package.json, pyproject.toml)
# Phase 2: Install dependencies (cached layer, only invalidates when deps change)
# Phase 3: Clone full source code (doesn't bust dependency cache)
# Phase 4-6: Install packages and verify (requires full source)
#
# BENEFITS: Dependency installation ~20-30 minutes is cached across source code changes
ARG CICD_BASE_IMAGE=kankali.darkhelm.lan:3001/darkhelm.org/plex-playlist-cicd-base:latest
FROM ${CICD_BASE_IMAGE}
# Build args for cache busting
ARG GITHUB_SHA
ENV GITHUB_SHA=${GITHUB_SHA}
LABEL org.opencontainers.image.revision=${GITHUB_SHA}
# Accept build arguments for Git checkout (no secrets here!)
ARG GITHUB_SHA
# Set working directory
WORKDIR /workspace
# OPTIMIZATION: Extract dependency files first for better layer caching
# Step 1: Clone repository minimally to get dependency files only
RUN --mount=type=secret,id=ssh_private_key \
mkdir -p ~/.ssh && \
cp /run/secrets/ssh_private_key ~/.ssh/id_rsa && \
chmod 600 ~/.ssh/id_rsa && \
echo "Host kankali.darkhelm.lan" > ~/.ssh/config && \
echo " Port 2222" >> ~/.ssh/config && \
echo " StrictHostKeyChecking no" >> ~/.ssh/config && \
echo " UserKnownHostsFile /dev/null" >> ~/.ssh/config && \
chmod 600 ~/.ssh/config && \
(ssh-keyscan -p 2222 kankali.darkhelm.lan >> ~/.ssh/known_hosts 2>/dev/null || echo "Warning: ssh-keyscan failed, continuing with StrictHostKeyChecking=no") && \
echo "=== Extracting dependency files for optimized caching ===" && \
GIT_SSH_COMMAND="ssh -F ~/.ssh/config" \
git -c core.autocrlf=false -c core.eol=lf clone --depth 1 --branch main \
ssh://git@kankali.darkhelm.lan:2222/DarkHelm.org/plex-playlist.git /tmp/repo && \
git -C /tmp/repo config core.autocrlf false && \
git -C /tmp/repo config core.eol lf && \
if [ -n "$GITHUB_SHA" ]; then \
cd /tmp/repo && \
if GIT_SSH_COMMAND="ssh -F ~/.ssh/config" git fetch --depth 1 origin "$GITHUB_SHA" >/tmp/git-fetch-sha.log 2>&1 && \
git checkout "$GITHUB_SHA" >/tmp/git-checkout-sha.log 2>&1; then \
echo "✓ Checked out requested GITHUB_SHA: $GITHUB_SHA"; \
else \
echo "❌ Failed to checkout requested GITHUB_SHA: $GITHUB_SHA"; \
echo "--- git fetch output ---"; \
cat /tmp/git-fetch-sha.log 2>/dev/null || true; \
echo "--- git checkout output ---"; \
cat /tmp/git-checkout-sha.log 2>/dev/null || true; \
exit 1; \
fi; \
fi && \
# Extract only dependency files for caching optimization
mkdir -p /workspace/backend /workspace/frontend && \
cp /tmp/repo/backend/pyproject.toml /workspace/backend/ 2>/dev/null || echo "No backend pyproject.toml" && \
cp /tmp/repo/frontend/package.json /workspace/frontend/ 2>/dev/null || echo "No frontend package.json" && \
cp /tmp/repo/frontend/yarn.lock /workspace/frontend/ 2>/dev/null || echo "No frontend yarn.lock" && \
cp /tmp/repo/frontend/.yarnrc.yml /workspace/frontend/ 2>/dev/null || echo "No frontend .yarnrc.yml" && \
cp /tmp/repo/.pre-commit-config.yaml /workspace/ 2>/dev/null || echo "No pre-commit config" && \
echo "✓ Dependency files extracted for optimized layer caching" && \
rm -rf ~/.ssh
# OPTIMIZATION PHASE 1: Install backend dependencies from extracted pyproject.toml
WORKDIR /workspace/backend
ENV VIRTUAL_ENV=/workspace/backend/.venv
# Install backend dependencies first (before source code) for better caching
RUN echo "=== Installing Backend Dependencies (Phase 1: Optimized Caching) ===" && \
# Create project virtual environment
uv venv $VIRTUAL_ENV && \
# Check if base image optimization is available
echo "=== Base Image Optimization Status ===" && \
if [ -f "/opt/python-dev-tools/bin/python" ]; then \
echo "✓ Found pre-installed Python dev tools - leveraging cache" && \
uv pip list --python /opt/python-dev-tools/bin/python --format=freeze > /tmp/base-tools.txt && \
echo "Available pre-installed tools:" && \
head -10 /tmp/base-tools.txt; \
else \
echo "⚠ Pre-installed Python dev tools not found - fresh installation" && \
echo "Base image may need rebuild for optimal caching"; \
fi && \
# Install dependencies from extracted pyproject.toml (this layer will cache!)
if [ -f "pyproject.toml" ]; then \
echo "Installing project dependencies from pyproject.toml..." && \
# Create minimal source structure to satisfy package build requirements
mkdir -p src/backend && \
echo "# Temporary README for dependency caching phase" > ../README.md && \
echo "# Minimal __init__.py for build" > src/backend/__init__.py && \
# Install all dependencies including dev dependencies
uv sync --dev && \
echo "✓ Backend dependencies installed and cached"; \
else \
echo "No pyproject.toml found, skipping dependency installation"; \
fi
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# OPTIMIZATION PHASE 2: Install frontend dependencies from extracted package.json
WORKDIR /workspace/frontend
# Setup frontend environment and install dependencies (before source code) for better caching
RUN echo "=== Installing Frontend Dependencies (Phase 2: Optimized Caching) ===" && \
echo "Available global tools (installed via npm):" && \
npm list -g --depth=0 2>/dev/null | head -10 || echo "Global npm tools available" && \
which tsc && which eslint && which prettier || echo "Global tools verified"
# Install frontend dependencies from extracted package.json (this layer will cache!)
RUN if [ -f "package.json" ]; then \
echo "Installing frontend dependencies from extracted package.json..." && \
export NODE_OPTIONS="--max-old-space-size=1024 --max-semi-space-size=64" && \
export UV_WORKERS=1 && \
echo "Memory info before install:" && \
free -h || true && \
INSTALL_SUCCESS=false && \
echo "Attempt 1/1: Installing project-specific frontend dependencies..." && \
echo "(Common dev tools pre-installed globally for performance)" && \
timeout 1200 yarn install --immutable --mode=skip-build \
&& INSTALL_SUCCESS=true || \
(echo "Attempt failed, cleaning up..." && \
rm -rf node_modules .yarn/cache .yarn/install-state.gz && \
yarn cache clean --all 2>/dev/null || true) && \
rm -rf .yarn/cache && \
if [ "$INSTALL_SUCCESS" = "false" ]; then \
echo "WARNING: Frontend dependencies installation failed"; \
echo "Continuing without frontend dependencies for CI/CD environment"; \
touch .frontend-deps-failed; \
else \
echo "✓ Frontend dependencies installed and cached"; \
fi; \
else \
echo "No package.json found, skipping frontend dependencies"; \
fi
# OPTIMIZATION PHASE 3: Reuse the repo cloned in phase 1 to avoid re-clone/session timeout
WORKDIR /workspace
RUN if [ ! -d "/tmp/repo/.git" ]; then \
echo "❌ Missing /tmp/repo clone from phase 1" && \
exit 1; \
fi
RUN echo "Copying source code while preserving installed dependencies..." && \
echo "Source files in repo:" && \
ls -la /tmp/repo/ && \
echo "Current workspace state:" && \
find /workspace -name "node_modules" -o -name ".venv" -o -name ".yarn" && \
echo "Copying source files (excluding dependencies)..." && \
for item in /tmp/repo/*; do \
basename_item=$(basename "$item"); \
if [ "$basename_item" = "backend" ] && [ -d "/workspace/backend/.venv" ]; then \
echo "Copying backend files while preserving .venv..."; \
find "$item" -mindepth 1 -maxdepth 1 ! -name ".venv" -exec cp -rf {} /workspace/backend/ \;; \
elif [ "$basename_item" = "frontend" ] && [ -d "/workspace/frontend/node_modules" ]; then \
echo "Copying frontend files (including dotfiles) while preserving node_modules..."; \
find "$item" -mindepth 1 -maxdepth 1 ! -name "node_modules" -exec cp -rf {} /workspace/frontend/ \;; \
else \
echo "Copying $basename_item..."; \
cp -rf "$item" /workspace/; \
fi; \
done && \
# Copy common hidden root files without touching . or ..
for dotfile in .dockerignore .gitignore .pre-commit-config.yaml .editorconfig; do \
if [ -f "/tmp/repo/${dotfile}" ]; then \
cp -f "/tmp/repo/${dotfile}" /workspace/; \
fi; \
done && \
echo "Final dependency check:" && \
find /workspace -name "node_modules" -o -name ".venv" -o -name ".yarn" && \
if [ -n "${GITHUB_SHA}" ]; then \
echo "${GITHUB_SHA}" > /workspace/.cicd-source-sha; \
fi && \
echo "✓ Full source code copied, dependencies preserved" && \
rm -rf /tmp/repo
# PHASE 3.5: Re-link Yarn after source code update (node-modules linker only)
WORKDIR /workspace/frontend
RUN if [ -f "package.json" ] && [ -d "node_modules" ]; then \
echo "=== Re-linking Yarn node_modules After Source Code Update ===" && \
echo "Ensuring package.json and installed node_modules are in sync..." && \
yarn install --immutable && \
echo "✓ Yarn node_modules re-linked successfully"; \
else \
echo " No node_modules detected, skipping Yarn re-link"; \
fi
# PHASE 4: Install backend package in development mode (requires full source)
WORKDIR /workspace/backend
RUN echo "=== Installing Backend Package in Development Mode ===" && \
uv pip install -e . && \
echo "✓ Backend package installed in development mode"
# PHASE 5: Install pre-commit environments (requires full source with .pre-commit-config.yaml)
WORKDIR /workspace
RUN echo "=== Installing Pre-commit Hook Environments ===" && \
if [ -f ".pre-commit-config.yaml" ]; then \
# pre-commit requires a git repo at runtime; keep it in the final image.
git config --global user.email "ci@cicd" && \
git config --global user.name "CICD" && \
git init /workspace && \
git -C /workspace add -A && \
cd /workspace/backend && \
if uv run pre-commit install-hooks; then \
echo "✓ Pre-commit hook environments installed successfully"; \
else \
echo "⚠ pre-commit hook prewarming failed; continuing"; \
fi; \
else \
echo "No .pre-commit-config.yaml found, skipping hook installation"; \
fi
# PHASE 6: Playwright browsers optimization check
WORKDIR /workspace/frontend
RUN if [ -f ".frontend-deps-failed" ]; then \
echo "Frontend dependencies failed - Playwright E2E tests will be skipped"; \
elif [ -f "package.json" ] && grep -q '@playwright/test' package.json && [ -d "node_modules" ]; then \
echo "Checking Playwright browser optimization status..." && \
# Check if Playwright CLI is available via yarn (from project dependencies)
if yarn playwright --version >/dev/null 2>&1; then \
echo "✓ Playwright CLI available via yarn" && \
PW_BROWSER_PATH="${PLAYWRIGHT_BROWSERS_PATH:-/root/.cache/ms-playwright}" && \
if find "${PW_BROWSER_PATH}" -maxdepth 1 -type d -name 'chromium-*' | grep -q .; then \
echo "✓ Playwright Chromium browser preinstalled at ${PW_BROWSER_PATH}"; \
else \
echo "⚠ Playwright Chromium not preinstalled at ${PW_BROWSER_PATH} - CI will install on demand"; \
fi; \
else \
echo "⚠ Playwright CLI not available - E2E setup will be handled in CI"; \
fi && \
echo "✓ Playwright environment checked"; \
else \
echo " No Playwright tests configured in this project"; \
fi
# Verify all tools are working with the project
RUN cd /workspace/backend && \
echo "=== Backend Tools Verification ===" && \
uv run ruff --version && \
uv run pyright --version && \
uv run pydoclint --version && \
uv run pytest --version && \
uv run yamllint --version && \
uv run toml-sort --version && \
uv run xdoctest --version && \
uv run pre-commit --version
RUN cd /workspace/frontend && \
echo "=== Frontend Tools Verification ===" && \
if [ -f ".frontend-deps-failed" ]; then \
echo "WARNING: Skipping frontend tool verification due to failed dependencies installation"; \
echo "Frontend CI/CD jobs may be limited in this environment"; \
elif [ -d "node_modules" ]; then \
verify_required_tool() { \
tool_name="$1"; \
yarn_target="$2"; \
fallback_bin="$3"; \
if yarn "${yarn_target}" --version; then \
return 0; \
fi; \
if command -v "${fallback_bin}" >/dev/null 2>&1; then \
"${fallback_bin}" --version; \
return 0; \
fi; \
echo "ERROR: ${tool_name} is not available via yarn or global fallback"; \
return 1; \
}; \
verify_optional_yarn_tool() { \
tool_name="$1"; \
yarn_target="$2"; \
if yarn "${yarn_target}" --version; then \
return 0; \
fi; \
echo "${tool_name} version check failed under Yarn wrapper; continuing"; \
return 0; \
}; \
echo "✓ node_modules dependencies found, verifying tools..." && \
verify_required_tool "eslint" "eslint" "eslint" && \
verify_required_tool "prettier" "prettier" "prettier" && \
verify_required_tool "typescript" "tsc" "tsc" && \
verify_optional_yarn_tool "vitest" "vitest" && \
echo "✓ All frontend tools verified successfully"; \
else \
echo "ERROR: No frontend node_modules found"; \
echo "Available files in frontend:"; \
ls -la .; \
exit 1; \
fi
# Set Python path for backend
ENV PYTHONPATH=/workspace/backend/src:/workspace/backend
# Make global development tools available in PATH for fallback
ENV PATH="/opt/python-dev-tools/bin:$PATH"
# Set working directory back to root
WORKDIR /workspace
# Default to bash
SHELL ["/bin/bash", "-c"]
CMD ["/bin/bash"]