Hyeonsang Cho efc87b6cb2
fix(deploy): let make up start on a checkout without .env files (#6370)
* fix(deploy): let make up start on a checkout without .env files

.env and frontend/.env are gitignored and make up does not create them,
but docker/docker-compose.yaml listed both as short-form env_file entries,
which Compose treats as required. On a fresh clone make up, make down and
make prod-logs all stopped with "env file .../.env not found" (exit 14)
before building or touching anything.

Mark the three entries optional with the long-form path/required syntax
the development compose file already uses. Compose loads them when they
exist and skips them otherwise; an unreadable or non-file .env still
fails. This needs Docker Compose 2.24+, the floor the README already
documents, and deploy.sh's startup-failure hint now names it for older
clients.

deploy.sh deliberately does not seed the files from the examples the way
docker.sh start does: .env.example carries placeholder API keys the
production Gateway would receive, and make config skips files that exist.

* docs(changelog): reference #6370

* test(deploy): gate Compose clients and cover make up

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-07 07:28:29 +08:00

42 lines
1.6 KiB
Python

"""Locate a Docker CLI whose Compose plugin can render the repo's compose files.
Tests that hand a compose file to the real client prove what Compose itself
accepts, which a YAML-shape assertion cannot. They skip without a compatible client.
"""
from __future__ import annotations
import re
import shutil
import subprocess
import pytest
MIN_COMPOSE_VERSION = (2, 24)
def find_docker_compose() -> tuple[str | None, str]:
"""Return a compatible ``docker`` executable and an empty or actionable skip reason."""
docker = shutil.which("docker")
if docker is None:
return None, "no docker compose client installed"
try:
probe = subprocess.run([docker, "compose", "version", "--short"], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=60, check=False)
except (OSError, subprocess.TimeoutExpired):
return None, "no docker compose client installed"
if probe.returncode != 0:
return None, "no docker compose client installed"
version = probe.stdout.strip()
requirement = f"Compose {MIN_COMPOSE_VERSION[0]}.{MIN_COMPOSE_VERSION[1]}+ required"
match = re.fullmatch(r"v?(\d+)\.(\d+)(?:\.\d+)?(?:[-+][a-zA-Z0-9.-]+)?", version)
if match is None:
return None, f"{requirement}, could not determine version from {version!r}"
if tuple(int(part) for part in match.groups()) < MIN_COMPOSE_VERSION:
return None, f"{requirement}, found {version}"
return docker, ""
DOCKER, _skip_reason = find_docker_compose()
requires_docker_compose = pytest.mark.skipif(DOCKER is None, reason=_skip_reason)