diff --git a/.devcontainer/.env b/.devcontainer/.env index d9abf75..5db7736 100644 --- a/.devcontainer/.env +++ b/.devcontainer/.env @@ -1,5 +1,36 @@ -PYTHON_BASE=3.13 -DEVCONTAINER_IMAGE_REV=0.1 -DEVCONTAINER_IMAGE_PULL_REPO=docker.masara.eu/python -DEVCONTAINER_IMAGE_PUSH_REPO=repo.masara.eu/python - +# Python version used in devcontainer image tag (e.g. 3.13). +PYTHON_BASE=3.13 + +# Image revision for versioned tag: :--devcontainer. +DEVCONTAINER_IMAGE_REV=0.1 + +# Registry/repository used by docker-compose to pull the devcontainer image. +DEVCONTAINER_IMAGE_PULL_REPO=docker.masara.eu/python + +# Registry/repository used by publish script to push the devcontainer image. +DEVCONTAINER_IMAGE_PUSH_REPO=repo.masara.eu/python + +# Private PyPI index URL used by pip inside the container. +PIP_INDEX_URL=https://dv.masara.eu/repository/pypi-group/simple + +# Allows running pip as root without interactive warning/failure. +PIP_ROOT_USER_ACTION=ignore + +# Workspace mount path inside container. +WORKSPACE_DIR=/workspace + +# Path to virtual environment created/used by post_create.sh. +VENV_PATH=/workspace/.venv + +# Requirements file installed by post_create.sh (if file exists). +REQUIREMENTS_FILE=/workspace/requirements.txt + + +# User used by VS Code remote server inside container (e.g. root). +DEVCONTAINER_RUN_USER=root + +# UID for app user created in project Dockerfile (non-devcontainer image build). +APP_USER_ID=1000 + +# Home directory for REMOTE_USER inside container. +REMOTE_HOME=/root diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 99b30e3..f2e7703 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,60 +1,49 @@ { - "name": "Python Dev", - "dockerComposeFile": "docker-compose.yml", - "service": "dev", - "containerEnv": { - "PIP_INDEX_URL": "https://dv.masara.eu/repository/pypi-group/simple", - "PIP_ROOT_USER_ACTION": "ignore" - }, - "remoteEnv": { - "DOCKER_BUILDKIT": "1", - "VIRTUAL_ENV": "/workspace/.venv", - "PATH": "/workspace/.venv/bin:${containerEnv:PATH}" - }, - "workspaceFolder": "/workspace", - "remoteUser": "root", - "overrideCommand": true, - "customizations": { - "vscode": { - "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance", - "ms-python.autopep8", - "ms-toolsai.jupyter", - "ms-python.black-formatter", - "ms-python.isort", - "codezombiech.gitignore", - "davidanson.vscode-markdownlint", - "ms-azuretools.vscode-docker", - "docker.docker", - "openai.chatgpt", - "continue.continue" - ], - "settings": { - "python.formatting.provider": "black", - "python.analysis.extraPaths": [ - "${workspaceFolder}/app" - ], - "editor.formatOnSave": true, - "python.terminal.activateEnvironment": true, - "python.defaultInterpreterPath": ".venv/bin/python", - "remote.restoreForwardedPorts": false, - "remote.autoForwardPortsSource": "output", - "debug.javascript.autoAttachFilter": "disabled" - } + "name": "Python Dev", + "dockerComposeFile": "docker-compose.yml", + "service": "dev", + "workspaceFolder": "/workspace", + "overrideCommand": true, + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.autopep8", + "ms-toolsai.jupyter", + "ms-python.black-formatter", + "ms-python.isort", + "codezombiech.gitignore", + "davidanson.vscode-markdownlint", + "ms-azuretools.vscode-docker", + "docker.docker", + "openai.chatgpt", + "continue.continue" + ], + "settings": { + "python.formatting.provider": "black", + "python.analysis.extraPaths": [ + "${workspaceFolder}/app" + ], + "python.analysis.typeCheckingMode": "basic", + "python.analysis.diagnosticMode": "workspace", + "editor.formatOnSave": true, + "python.terminal.activateEnvironment": true, + "python.defaultInterpreterPath": "${env:VENV_PATH}/bin/python", + "remote.restoreForwardedPorts": false, + "remote.autoForwardPortsSource": "output", + "debug.javascript.autoAttachFilter": "disabled" + } + } + }, + "mounts": [ + "type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock" + ], + "postCreateCommand": "bash .devcontainer/post_create.sh", + "forwardPorts": [], + "portsAttributes": { + "*": { + "onAutoForward": "ignore" + } } - }, - "mounts": [ - "type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock", - "type=volume,source=docextractor-codex,target=/root/.codex", - "type=bind,source=${localEnv:USERPROFILE}/.codex/auth.json,target=/root/.codex/auth.json,readonly", - "type=bind,source=${localEnv:USERPROFILE}/.pypirc,target=/root/.pypirc,readonly" - ], - "postCreateCommand": "bash .devcontainer/post_create.sh", - "forwardPorts": [], - "portsAttributes": { - "*": { - "onAutoForward": "ignore" - } - } -} +} \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 50e46ec..faa2052 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,11 +1,22 @@ -services: +services: dev: image: ${DEVCONTAINER_IMAGE_PULL_REPO}:${PYTHON_BASE}-${DEVCONTAINER_IMAGE_REV}-devcontainer pull_policy: always init: true + user: ${DEVCONTAINER_RUN_USER:-root} command: sleep infinity tty: true volumes: - ..:/workspace:cached + - docextractor-codex:${REMOTE_HOME:-/root}/.codex + - ${USERPROFILE}/.codex/auth.json:${REMOTE_HOME:-/root}/.codex/auth.json:ro + - ${USERPROFILE}/.pypirc:${REMOTE_HOME:-/root}/.pypirc:ro environment: - PYTHON_BASE: ${PYTHON_BASE} + PIP_INDEX_URL: ${PIP_INDEX_URL} + PIP_ROOT_USER_ACTION: ${PIP_ROOT_USER_ACTION} + WORKSPACE_DIR: ${WORKSPACE_DIR} + VENV_PATH: ${VENV_PATH} + REQUIREMENTS_FILE: ${REQUIREMENTS_FILE} + +volumes: + docextractor-codex: \ No newline at end of file diff --git a/.devcontainer/post_create.sh b/.devcontainer/post_create.sh index ffa6d69..781c66d 100644 --- a/.devcontainer/post_create.sh +++ b/.devcontainer/post_create.sh @@ -1,7 +1,18 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -VENV_PATH="/workspace/.venv" +WORKSPACE_DIR="${WORKSPACE_DIR:-/workspace}" +VENV_PATH="${VENV_PATH:-$WORKSPACE_DIR/.venv}" +REQUIREMENTS_FILE="${REQUIREMENTS_FILE:-$WORKSPACE_DIR/requirements.txt}" +# Resolve current user's home robustly (works for root and non-root users). +USER_HOME="${HOME:-}" +if [ -z "$USER_HOME" ] && command -v getent >/dev/null 2>&1; then + USER_HOME="$(getent passwd "$(id -un)" | cut -d: -f6)" +fi +if [ -z "$USER_HOME" ]; then + USER_HOME="/root" +fi +BASHRC_PATH="${BASHRC_PATH:-$USER_HOME/.bashrc}" # Ak venv neexistuje, vytvor ho a priprav pip if [ ! -d "$VENV_PATH" ]; then @@ -19,17 +30,17 @@ echo "Aktualizujem pip a základné nástroje..." python -m pip install --upgrade pip setuptools wheel # Inštalácia závislostí, ak existuje requirements.txt -if [ -f "/workspace/requirements.txt" ]; then +if [ -f "$REQUIREMENTS_FILE" ]; then echo "Inštalujem závislosti z requirements.txt..." - python -m pip install -r /workspace/requirements.txt + python -m pip install -r "$REQUIREMENTS_FILE" else echo "requirements.txt nenájdený – preskakujem inštaláciu závislostí." fi # Ak sa terminál otvorí skôr, než Python extension stihne auto-aktiváciu, # zabezpečíme aktiváciu aj cez shell init. -BASHRC="/root/.bashrc" -ACTIVATE_LINE='[ -f /workspace/.venv/bin/activate ] && source /workspace/.venv/bin/activate' -if ! grep -Fq "$ACTIVATE_LINE" "$BASHRC"; then - echo "$ACTIVATE_LINE" >> "$BASHRC" +ACTIVATE_LINE="[ -f \"$VENV_PATH/bin/activate\" ] && source \"$VENV_PATH/bin/activate\"" +touch "$BASHRC_PATH" +if ! grep -Fq "$ACTIVATE_LINE" "$BASHRC_PATH"; then + echo "$ACTIVATE_LINE" >> "$BASHRC_PATH" fi diff --git a/Dockerfile b/Dockerfile index 344e20d..d66b3a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,20 @@ # syntax=docker/dockerfile:1.6 -ARG PYTHON_BASE=3.13 -FROM python:${PYTHON_BASE}-slim +ARG PYTHON_BASE=3.13 +ARG PIP_INDEX_URL=https://dv.masara.eu/repository/pypi-group/simple +ARG APP_USER_ID=1000 +FROM python:${PYTHON_BASE}-slim + +# Re-declare build args for use in this build stage. +ARG PIP_INDEX_URL +ARG APP_USER_ID -ENV PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 \ - PIP_NO_CACHE_DIR=1 \ - PIP_DISABLE_PIP_VERSION_CHECK=1 \ - PIP_INDEX_URL=https://dv.masara.eu/repository/pypi-group/simple \ - PIP_NO_INPUT=1 +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_INDEX_URL=${PIP_INDEX_URL} \ + PIP_NO_INPUT=1 WORKDIR /app @@ -21,9 +27,8 @@ RUN --mount=type=cache,target=/root/.cache/pip \ COPY app/ /app/ # Vytvorenie ne-root usera -ARG APP_USER_ID=1000 -RUN useradd -u ${APP_USER_ID} -m appuser && \ - chown -R appuser:appuser /app +RUN useradd -u ${APP_USER_ID} -m appuser && \ + chown -R appuser:appuser /app USER appuser diff --git a/README.md b/README.md index 618beaa..34035ed 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,34 @@ Template pre Python projekt s Dev Container workflow, kde sa devcontainer image - devcontainer konfiguraciu (`.devcontainer/devcontainer.json`) - compose definiciu pre devcontainer (`.devcontainer/docker-compose.yml`) -- centralne nastavenie verzie Pythonu a image tagov (`.devcontainer/.env`) +- centralne nastavenie premennych (`.devcontainer/.env`) - skript na build/push devcontainer image (`scripts/publish-devcontainer-image.ps1`) - VS Code tasky na build/push (`.vscode/tasks.json`) -- vzorovy `Dockerfile` pre aplikaciu (`Dockerfile`) +- hlavny `Dockerfile` pre aplikacny image (`Dockerfile`) -## Centralne nastavenie verzie a repo +## Konfiguracia cez `.devcontainer/.env` -Vsetko sa riadi cez `.devcontainer/.env`: +`.devcontainer/.env` je centralne miesto pre devcontainer premenne. +Pouziva ho: -- `PYTHON_BASE` - verzia Pythonu pre devcontainer image -- `DEVCONTAINER_IMAGE_PULL_REPO` - odkial sa image taha pri otvoreni devcontainera (napr. `docker.masara.eu/python`) -- `DEVCONTAINER_IMAGE_PUSH_REPO` - kam sa image pushuje pri publikovani (napr. `repo.masara.eu/python`) -- `DEVCONTAINER_IMAGE_REV` - revizia image (napr. `0.1`) +- `.devcontainer/docker-compose.yml` (image tag, user, volume cesty, container environment) +- `scripts/publish-devcontainer-image.ps1` (tagovanie a push devcontainer image) +- `Dockerfile` aplikacie (volitelne cez `--build-arg`) + +Aktualne premenne: + +- `PYTHON_BASE` - verzia Pythonu pre devcontainer image tag +- `DEVCONTAINER_IMAGE_REV` - revizia devcontainer image +- `DEVCONTAINER_IMAGE_PULL_REPO` - registry/repo odkial sa image taha pri otvoreni devcontainera +- `DEVCONTAINER_IMAGE_PUSH_REPO` - registry/repo kam sa image publikuje +- `PIP_INDEX_URL` - pip index URL v kontajneri +- `PIP_ROOT_USER_ACTION` - pip spravanie pri root userovi +- `WORKSPACE_DIR` - workspace cesta v kontajneri +- `VENV_PATH` - cesta k virtualnemu prostrediu +- `REQUIREMENTS_FILE` - cesta k requirements suboru pre `post_create.sh` +- `DEVCONTAINER_RUN_USER` - Linux user, pod ktorym bezi devcontainer service +- `APP_USER_ID` - UID usera `appuser` v hlavnom `Dockerfile` +- `REMOTE_HOME` - home adresar pouzity pre `.codex` a `.pypirc` mounty ## Build a publish devcontainer image @@ -29,8 +44,8 @@ Predpoklady: ### Cez VS Code task -1. Spusti task `devcontainer: build and push image`. -2. Task zavola skript `scripts/publish-devcontainer-image.ps1 -Push`. +1. Spusti task `devcontainer: build image`. +2. Alebo spusti `devcontainer: build and push image` pre build + push. ### Cez terminal @@ -44,27 +59,62 @@ powershell -ExecutionPolicy Bypass -File scripts/publish-devcontainer-image.ps1 Poznamka: -- Ak `docker push` zlyha (napr. neautorizovany pristup), skript spravi fallback `docker login ` a push zopakuje este raz. +- Ak `docker push` zlyha (napr. neautorizovany pristup), skript skusi `docker login ` a push zopakuje este raz. -## Pouzitie pri vytvoreni/otvoreni projektu +## Pouzitie vo VS Code -1. Naklonuj alebo vytvor projekt z tejto sablony. -2. Nastav hodnoty v `.devcontainer/.env` (hlavne `PYTHON_BASE`, pull/push repo, revision). -3. Ak menis base image (napr. Python verziu), publikuj novu verziu devcontainer image do push registry. -4. Otvor projekt vo VS Code a pouzi `Reopen in Container`. +1. Nastav hodnoty v `.devcontainer/.env`. +2. Ak si menil `PYTHON_BASE` alebo image repo/rev, najprv publikuj novu devcontainer image. +3. Otvor projekt vo VS Code a pouzi `Reopen in Container`. -Devcontainer sa spusti z image definovaneho v `.devcontainer/docker-compose.yml`: +Pri starte kontajnera: - image sa taha z `DEVCONTAINER_IMAGE_PULL_REPO` -- workspace je pripojeny bind mountom do `/workspace` -- `postCreateCommand` vytvori `.venv` a nainstaluje zavislosti z `requirements.txt` +- workspace sa mountuje do `/workspace` +- `postCreateCommand` (`.devcontainer/post_create.sh`) vytvori `.venv` a nainstaluje zavislosti z `REQUIREMENTS_FILE` +- Python vo VS Code pouziva `${env:VENV_PATH}/bin/python` -## Bezne scenare +## VS Code extensions a settings -- Chcem iba zmenit Python verziu: - 1. Zmen `PYTHON_BASE` v `.devcontainer/.env`. - 2. Spusti `devcontainer: build and push image`. - 3. Vo VS Code daj `Rebuild/Reopen in Container`. +Devcontainer automaticky instaluje tieto VS Code extensions: -- Chcem zmenit iba aplikacny kod: - - Nerebuildi sa devcontainer image, staci reopen/restart kontajnera podla potreby. +- `ms-python.python` +- `ms-python.vscode-pylance` +- `ms-python.autopep8` +- `ms-toolsai.jupyter` +- `ms-python.black-formatter` +- `ms-python.isort` +- `codezombiech.gitignore` +- `davidanson.vscode-markdownlint` +- `ms-azuretools.vscode-docker` +- `docker.docker` +- `openai.chatgpt` +- `continue.continue` + +Pouzite VS Code settings v devcontainery: + +- `python.formatting.provider: black` +- `python.analysis.extraPaths: ["${workspaceFolder}/app"]` +- `python.analysis.typeCheckingMode: "basic"` +- `python.analysis.diagnosticMode: "workspace"` +- `editor.formatOnSave: true` +- `python.terminal.activateEnvironment: true` +- `python.defaultInterpreterPath: "${env:VENV_PATH}/bin/python"` +- `remote.restoreForwardedPorts: false` +- `remote.autoForwardPortsSource: "output"` +- `debug.javascript.autoAttachFilter: "disabled"` + +## Aplikacny Dockerfile + +Hlavny `Dockerfile` podporuje build argumenty `PYTHON_BASE`, `PIP_INDEX_URL` a `APP_USER_ID`. + +Priklad buildu s hodnotami z `.devcontainer/.env`: + +```bash +set -a; source .devcontainer/.env; set +a +docker build \ + --build-arg PYTHON_BASE="$PYTHON_BASE" \ + --build-arg PIP_INDEX_URL="$PIP_INDEX_URL" \ + --build-arg APP_USER_ID="$APP_USER_ID" \ + -t myapp:local . +```