# syntax=docker/dockerfile:1.7
FROM golang:1.26.6-alpine AS builder

WORKDIR /app

# Use the builder image's bundled Go toolchain, never an auto-downloaded one.
# GOTOOLCHAIN=local future-proofs a base bump (a hardcoded version would silently
# download+reuse the old toolchain) and gives the build a distinct cache key so a
# stale registry-cached go-build layer can't be reused (see the cloud-collector/
# llm-server toolchain-cache fix). The copied /usr/local/go in the final stage
# therefore also matches the builder.
ENV GOTOOLCHAIN=local

# Install git and other dependencies needed for build and runtime
RUN apk add --no-cache git make ca-certificates openssh-client

# Copy go mod and sum files
COPY go.mod go.sum ./

# Download dependencies
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go mod download

# Copy source code
COPY . .

# Build the application
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux go build -o code-analysis-agent ./cmd/main.go
# Build the shim
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux go build -o shim ./cmd/shim/main.go

# Install golangci-lint in builder where we have the right Go version
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux GOBIN=/usr/local/bin \
    go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.9.0

# Install the GitLab CLI (glab) from source rather than `apk add glab`.
# Two reasons: (1) Alpine v3.22 community pins glab at 1.58.0-r5, which is far
# behind, and (2) llm-server's base image has no glab apt package and builds it
# from source too — building both from the same pinned version keeps the
# workspace pod and llm-server's local-exec fallback on identical glab, so a
# prompt that works in one works in the other. `gh` is the counter-example to
# avoid: apk `github-cli` here vs a source build there, free to drift.
# Consumed by llm-server's gitlab_execute tool (tools/tool_gitlab.go) and by
# this service's own glab tool (tools/glab_tool.go).
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux GOBIN=/usr/local/bin \
    go install gitlab.com/gitlab-org/cli/cmd/glab@v1.112.0

# Final stage
FROM alpine:3.22

WORKDIR /app

# Install runtime dependencies
# git, curl, openssh-client, bind-tools (dig), iputils (ping), net-tools (netstat), nmap-ncat (nc)
# jq, grep, findutils, coreutils, sed, awk, tar, unzip, procps
# ripgrep (rg), the_silver_searcher (ag)
# OS-package cache-bust: bumping OS_PKG_EPOCH (ISO week) changes this layer's
# cache key so BuildKit re-runs the apk install and pulls current Alpine security
# patches instead of a stale cached layer. Committed (not a build-arg) so each
# refresh is a reviewable, revertable diff; bump when the Trivy scan flags a stale
# package (e.g. c-ares CVE-2026-33630); currently W36 pulls patched packages (libcrypto3 3.5.8-r0).
ARG OS_PKG_EPOCH=2026-W36
RUN echo "os-pkg-epoch: ${OS_PKG_EPOCH}" \
    && apk upgrade --no-cache \
    && apk add --no-cache \
    git \
    curl \
    openssh-client \
    bind-tools \
    iputils \
    net-tools \
    nmap-ncat \
    jq \
    grep \
    findutils \
    coreutils \
    sed \
    gawk \
    tar \
    unzip \
    procps \
    ripgrep \
    the_silver_searcher \
    ca-certificates \
    github-cli \
    aws-cli \
    python3 \
    py3-pip \
    py3-pyflakes \
    bash

# Install Google Cloud SDK
RUN ARCH=$(uname -m) && \
    if [ "$ARCH" = "x86_64" ]; then \
        curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz && \
        tar -xf google-cloud-cli-linux-x86_64.tar.gz && \
        rm google-cloud-cli-linux-x86_64.tar.gz; \
    elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then \
        curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-arm.tar.gz && \
        tar -xf google-cloud-cli-linux-arm.tar.gz && \
        rm google-cloud-cli-linux-arm.tar.gz; \
    fi && \
    ./google-cloud-sdk/install.sh --quiet --usage-reporting false --path-update false --command-completion false && \
    ./google-cloud-sdk/bin/gcloud components install beta alpha --quiet && \
    rm -rf ./google-cloud-sdk/.install/.backup ./google-cloud-sdk/.install/.download && \
    rm -rf ./google-cloud-sdk/platform/bundledpythonunix ./google-cloud-sdk/bin/gcloud-crc32c && \
    rm -rf /root/.config/gcloud
# Run gcloud on the system interpreter (which carries cryptography>=48, installed
# below) instead of the SDK's bundled python. The bundle ships a frozen
# cryptography (46.x) + pip that Google patches only on its own cadence, so it
# accrued HIGH/MEDIUM CVEs we cannot apt/apk-upgrade; deleting it removes them.
# gcloud-crc32c is a Go binary that only accelerates `gcloud storage` transfers
# (unused here — GCS access goes through the Go SDK), so it is dead weight
# carrying stdlib CVEs from Google's build toolchain. Validated: gcloud resolves
# /usr/bin/python3 and the service-account auth crypto path works with the bundle
# removed.
ENV CLOUDSDK_PYTHON=/usr/bin/python3
ENV PATH=/home/appuser/.local/bin:/home/appuser/bin:/app/google-cloud-sdk/bin:$PATH
# Keep dependency/build caches in explicit, workspace-owned locations so the
# runtime can share them across worktrees and remove them safely when idle.
ENV GOCACHE=/tmp/code-analysis/shared-cache/go-build \
    GOMODCACHE=/tmp/code-analysis/shared-cache/go-mod \
    GOLANGCI_LINT_CACHE=/tmp/code-analysis/shared-cache/go-lint \
    PIP_CACHE_DIR=/tmp/code-analysis/shared-cache/python/pip \
    PYTHONPYCACHEPREFIX=/tmp/code-analysis/shared-cache/python/pycache \
    npm_config_cache=/tmp/code-analysis/shared-cache/node/npm \
    PNPM_HOME=/tmp/code-analysis/shared-cache/node/pnpm \
    MAVEN_OPTS=-Dmaven.repo.local=/tmp/code-analysis/shared-cache/maven \
    GRADLE_USER_HOME=/tmp/code-analysis/shared-cache/gradle

# Install Azure CLI in a venv to isolate its dependencies (e.g. urllib3 2.x)
# from system Python packages used by apk-installed aws-cli
RUN apk add --no-cache --virtual .build-deps \
    gcc \
    musl-dev \
    python3-dev \
    libffi-dev \
    openssl-dev \
    cargo \
    make \
    linux-headers && \
    python3 -m venv /opt/azure-cli-venv && \
    /opt/azure-cli-venv/bin/pip install --no-cache-dir --upgrade pip && \
    /opt/azure-cli-venv/bin/pip install --no-cache-dir azure-cli "cryptography>=48.0.1" && \
    ln -s /opt/azure-cli-venv/bin/az /usr/local/bin/az && \
    pip install --no-cache-dir --break-system-packages --upgrade "cryptography>=48.0.1" && \
    apk del .build-deps

# Pre-install Azure CLI extensions to a fixed directory.
# AZURE_EXTENSION_DIR decouples extension storage from AZURE_CONFIG_DIR,
# ensuring extensions remain available when AZURE_CONFIG_DIR is overridden
# per-session for auth isolation. Without this, dynamic extension installs
# (e.g. log-analytics) trigger a CLI re-execution that loses login state.
ENV AZURE_EXTENSION_DIR=/opt/azure-cli-extensions
RUN mkdir -p /opt/azure-cli-extensions && \
    az extension add --name log-analytics && \
    az extension add --name costmanagement

# Install build & development tools for code verification
# These enable the agent to run build/lint/test commands on cloned repositories.
# Go is copied from the builder stage instead of `apk add go` because Alpine
# ships an older Go than the builder, which fails on target repos whose go.mod
# requires a newer toolchain (e.g. nudgebee/api-server/services requires >= 1.26.1).
RUN apk add --no-cache \
    make \
    nodejs \
    npm

# Install common linters and package managers
# Python tools are installed in a venv to avoid polluting system site-packages
# (e.g. poetry pulls in urllib3 2.x which breaks apk-installed aws-cli)
RUN npm install -g yarn pnpm && \
    python3 -m venv /opt/python-tools && \
    /opt/python-tools/bin/pip install --no-cache-dir --upgrade pip && \
    /opt/python-tools/bin/pip install --no-cache-dir poetry black flake8 mypy && \
    ln -s /opt/python-tools/bin/poetry /usr/local/bin/poetry && \
    ln -s /opt/python-tools/bin/black /usr/local/bin/black && \
    ln -s /opt/python-tools/bin/flake8 /usr/local/bin/flake8 && \
    ln -s /opt/python-tools/bin/mypy /usr/local/bin/mypy

# Patch pnpm's vendored node-tar.
# pnpm vendors its own copy of node-tar, and 11.17.0 shipped tar 7.5.20 carrying
# GHSA-r292-9mhp-454m (uncontrolled recursion in mapHas/filesFilter -> uncatchable
# stack-overflow DoS when extracting a crafted tarball). pnpm is a supported build
# runner here (orchestrator_agent.go offers `pnpm lint/build/test` on cloned
# repos), so overwrite whatever vendored copies the installed pnpm ships.
# tar is pure JS and this is a patch-level bump, so pnpm's usage is unaffected.
#
# The copies are discovered, not hardcoded: pnpm used to bundle a second one under
# artifacts/exe/dist/ and dropped it in 11.24.0, which broke the earlier fixed-path
# check and failed the release build. Finding zero copies is a pass (nothing
# vendored means nothing vulnerable); every copy that is found must report
# TAR_VERSION afterwards. Bump TAR_VERSION when the Trivy scan flags it again.
ARG TAR_VERSION=7.5.22
RUN npm install --no-save --prefix /tmp/tarfix "tar@${TAR_VERSION}" \
    && for d in $(find /usr/local/lib/node_modules/pnpm \
                    -type d -path '*/node_modules/tar' -prune -print); do \
         rm -rf "$d" && cp -R /tmp/tarfix/node_modules/tar "$d"; \
       done \
    && rm -rf /tmp/tarfix \
    && for d in $(find /usr/local/lib/node_modules/pnpm \
                    -type d -path '*/node_modules/tar' -prune -print); do \
         grep -q "\"version\": \"${TAR_VERSION}\"" "$d/package.json" \
           || { echo "ERROR: unpatched pnpm vendored tar at $d"; exit 1; }; \
       done

# Copy Go toolchain and golangci-lint from builder.
# Builder uses golang:1.26-alpine; matching that here keeps build/vet runnable
# against modules that pin go >= 1.26.
COPY --from=builder /usr/local/go /usr/local/go
ENV PATH=/usr/local/go/bin:$PATH
COPY --from=builder /usr/local/bin/golangci-lint /usr/local/bin/golangci-lint
COPY --from=builder /usr/local/bin/glab /usr/local/bin/glab

# Create a non-root user
RUN adduser -D -g '' appuser

# Create workspace directory and set ownership
RUN mkdir -p /tmp/code-analysis && \
    mkdir -p /app/workspaces && \
    chown -R appuser:appuser /tmp/code-analysis /app /opt/azure-cli-extensions

# Copy binary from builder
COPY --from=builder /app/code-analysis-agent .
COPY --from=builder /app/shim /usr/local/bin/shim
RUN chown appuser:appuser ./code-analysis-agent /usr/local/bin/shim

# Create symlinks for shims
RUN ln -s /usr/local/bin/shim /usr/local/bin/kubectl && \
    ln -s /usr/local/bin/shim /usr/local/bin/helm && \
    ln -s /usr/local/bin/shim /usr/local/bin/psql && \
    ln -s /usr/local/bin/shim /usr/local/bin/mysql && \
    ln -s /usr/local/bin/shim /usr/local/bin/redis-cli && \
    ln -s /usr/local/bin/shim /usr/local/bin/argocd && \
    ln -s /usr/local/bin/shim /usr/local/bin/clickhouse-client && \
    ln -s /usr/local/bin/shim /usr/local/bin/rabbitmqadmin && \
    ln -s /usr/local/bin/shim /usr/local/bin/rabbitmq-api && \
    ln -s /usr/local/bin/shim /usr/local/bin/sqlcmd && \
    ln -s /usr/local/bin/shim /usr/local/bin/sqlplus && \
    ln -s /usr/local/bin/shim /usr/local/bin/ssh

# Switch to non-root user
USER appuser

# Expose port
EXPOSE 8080

# Run the application
CMD ["/app/code-analysis-agent"]
