build(v16): lock multi-arch r4 inputs
This commit is contained in:
@@ -1,35 +1,51 @@
|
||||
ARG FRAPPE_BRANCH=version-16
|
||||
ARG FRAPPE_IMAGE_PREFIX=frappe
|
||||
ARG FRAPPE_BRANCH=v16.23.1
|
||||
ARG FRAPPE_BUILD_IMAGE=frappe/build:version-16@sha256:ca022851cd8babd508e76ed36fc61d60776d2c3b938d869cf12eb6df16716e58
|
||||
ARG FRAPPE_BASE_IMAGE=frappe/base:version-16@sha256:0a43692eeb9c2f18674269ece2811ffb0f0543556fc701b77727aeeb587bb0e0
|
||||
|
||||
FROM ${FRAPPE_IMAGE_PREFIX}/build:${FRAPPE_BRANCH} AS builder
|
||||
FROM ${FRAPPE_BUILD_IMAGE} AS builder
|
||||
|
||||
ARG FRAPPE_BRANCH=version-16
|
||||
ARG FRAPPE_BRANCH=v16.23.1
|
||||
ARG FRAPPE_PATH=https://github.com/frappe/frappe
|
||||
ARG CACHE_BUST=""
|
||||
|
||||
COPY --chown=frappe:frappe \
|
||||
manifests/apps.v16.prod.lock.json \
|
||||
/opt/enuxia/apps.v16.prod.lock.json
|
||||
|
||||
COPY --chown=frappe:frappe \
|
||||
scripts/clone-locked-apps.py \
|
||||
/opt/enuxia/clone-locked-apps.py
|
||||
|
||||
COPY --chown=frappe:frappe \
|
||||
patches/crm/event.py \
|
||||
/opt/enuxia-patches/crm/event.py
|
||||
|
||||
USER frappe
|
||||
|
||||
RUN --mount=type=secret,id=apps_json,target=/opt/frappe/apps.json,uid=1000,gid=1000 \
|
||||
set -eux; \
|
||||
RUN set -eux; \
|
||||
: "${CACHE_BUST}"; \
|
||||
APP_INSTALL_ARGS=""; \
|
||||
if [ -s /opt/frappe/apps.json ]; then \
|
||||
APP_INSTALL_ARGS="--apps_path=/opt/frappe/apps.json"; \
|
||||
fi; \
|
||||
bench init ${APP_INSTALL_ARGS} \
|
||||
bench init \
|
||||
--frappe-branch="${FRAPPE_BRANCH}" \
|
||||
--frappe-path="${FRAPPE_PATH}" \
|
||||
--no-procfile \
|
||||
--no-backups \
|
||||
--skip-redis-config-generation \
|
||||
--skip-assets \
|
||||
--verbose \
|
||||
/home/frappe/frappe-bench; \
|
||||
cd /home/frappe/frappe-bench; \
|
||||
echo "{}" > sites/common_site_config.json; \
|
||||
printf '%s\n' '{}' > sites/common_site_config.json
|
||||
|
||||
RUN set -eux; \
|
||||
python3 /opt/enuxia/clone-locked-apps.py \
|
||||
/opt/enuxia/apps.v16.prod.lock.json \
|
||||
/home/frappe/frappe-bench; \
|
||||
cd /home/frappe/frappe-bench; \
|
||||
cp /opt/enuxia/apps.v16.prod.lock.json enuxia-build-lock.json; \
|
||||
bench setup requirements
|
||||
|
||||
RUN set -eux; \
|
||||
cd /home/frappe/frappe-bench; \
|
||||
\
|
||||
echo "=== Correctif LMS ==="; \
|
||||
LMS_SOURCE="apps/lms/lms/lms/patches/v2_0/add_video_watch_duration_index.py"; \
|
||||
@@ -64,14 +80,22 @@ RUN --mount=type=secret,id=apps_json,target=/opt/frappe/apps.json,uid=1000,gid=1
|
||||
"${CRM_TARGET}" \
|
||||
"${GAMEPLAN_FILE}"; \
|
||||
\
|
||||
rm -rf /opt/enuxia-patches; \
|
||||
find apps -mindepth 1 -path "*/.git" -prune -exec rm -rf {} +
|
||||
bench set-config -gp socketio_port 9000; \
|
||||
bench build --production; \
|
||||
bench version > enuxia-build-versions.txt; \
|
||||
cp sites/apps.txt enuxia-build-apps.txt; \
|
||||
find apps \
|
||||
-mindepth 2 \
|
||||
-type d \
|
||||
-name .git \
|
||||
-prune \
|
||||
-exec rm -rf {} +
|
||||
|
||||
FROM ${FRAPPE_IMAGE_PREFIX}/base:${FRAPPE_BRANCH} AS backend
|
||||
FROM ${FRAPPE_BASE_IMAGE} AS backend
|
||||
|
||||
LABEL org.opencontainers.image.title="Enuxia Suite"
|
||||
LABEL org.opencontainers.image.description="Production Frappe and ERPNext v16 image maintained by Enuxia"
|
||||
LABEL org.opencontainers.image.version="16.23.1-r3"
|
||||
LABEL org.opencontainers.image.description="Production Frappe and ERPNext v16 multi-architecture image maintained by Enuxia"
|
||||
LABEL org.opencontainers.image.version="16.23.1-r4"
|
||||
LABEL org.opencontainers.image.source="https://git.enuxia.fr/Enuxia-Public/enuxia-suite"
|
||||
|
||||
USER frappe
|
||||
@@ -87,19 +111,27 @@ RUN cp -r \
|
||||
/home/frappe/frappe-bench/assets && \
|
||||
rm -rf /home/frappe/frappe-bench/sites/assets
|
||||
|
||||
VOLUME [ \
|
||||
"/home/frappe/frappe-bench/sites", \
|
||||
"/home/frappe/frappe-bench/logs" \
|
||||
]
|
||||
VOLUME ["/home/frappe/frappe-bench/sites", "/home/frappe/frappe-bench/logs"]
|
||||
|
||||
USER root
|
||||
|
||||
RUN mkdir -p /opt/enuxia/manifests
|
||||
|
||||
COPY manifests/apps.v16.prod.lock.json \
|
||||
/opt/enuxia/manifests/apps.v16.prod.lock.json
|
||||
|
||||
COPY manifests/base-images.v16.prod.lock.json \
|
||||
/opt/enuxia/manifests/base-images.v16.prod.lock.json
|
||||
|
||||
COPY resources/core/main-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
COPY resources/core/start.sh /usr/local/bin/start.sh
|
||||
|
||||
RUN chmod 755 \
|
||||
/usr/local/bin/entrypoint.sh \
|
||||
/usr/local/bin/start.sh
|
||||
RUN chmod 0444 \
|
||||
/opt/enuxia/manifests/apps.v16.prod.lock.json \
|
||||
/opt/enuxia/manifests/base-images.v16.prod.lock.json && \
|
||||
chmod 0755 \
|
||||
/usr/local/bin/entrypoint.sh \
|
||||
/usr/local/bin/start.sh
|
||||
|
||||
USER frappe
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"build": {
|
||||
"reference": "frappe/build:version-16",
|
||||
"digest": "sha256:ca022851cd8babd508e76ed36fc61d60776d2c3b938d869cf12eb6df16716e58"
|
||||
},
|
||||
"base": {
|
||||
"reference": "frappe/base:version-16",
|
||||
"digest": "sha256:0a43692eeb9c2f18674269ece2811ffb0f0543556fc701b77727aeeb587bb0e0"
|
||||
}
|
||||
}
|
||||
Executable
+160
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
COMMIT_RE = re.compile(r"^[0-9a-f]{40}$")
|
||||
|
||||
|
||||
def command(args, cwd=None, capture=False):
|
||||
result = subprocess.run(
|
||||
args,
|
||||
cwd=cwd,
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE if capture else None,
|
||||
)
|
||||
return result.stdout.strip() if capture else ""
|
||||
|
||||
|
||||
def load_lock(path):
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
if not isinstance(data, dict):
|
||||
raise SystemExit("Le verrou doit être un objet JSON.")
|
||||
|
||||
if "frappe" not in data:
|
||||
raise SystemExit("Entrée frappe absente du verrou.")
|
||||
|
||||
entries = []
|
||||
|
||||
for name, metadata in data.items():
|
||||
if not isinstance(metadata, dict):
|
||||
raise SystemExit(f"Métadonnées invalides pour {name}.")
|
||||
|
||||
resolution = metadata.get("resolution") or {}
|
||||
url = metadata.get("url")
|
||||
branch = resolution.get("branch")
|
||||
commit = resolution.get("commit_hash")
|
||||
idx = metadata.get("idx")
|
||||
|
||||
if not isinstance(url, str) or not url.startswith("https://"):
|
||||
raise SystemExit(f"URL publique invalide pour {name}: {url!r}")
|
||||
|
||||
if not isinstance(branch, str) or not branch:
|
||||
raise SystemExit(f"Branche absente pour {name}.")
|
||||
|
||||
if not isinstance(commit, str) or not COMMIT_RE.fullmatch(commit):
|
||||
raise SystemExit(f"Commit invalide pour {name}: {commit!r}")
|
||||
|
||||
if not isinstance(idx, int):
|
||||
raise SystemExit(f"Index invalide pour {name}: {idx!r}")
|
||||
|
||||
entries.append((name, metadata))
|
||||
|
||||
entries.sort(key=lambda item: item[1]["idx"])
|
||||
|
||||
expected = list(range(1, len(entries) + 1))
|
||||
actual = [metadata["idx"] for _, metadata in entries]
|
||||
|
||||
if actual != expected:
|
||||
raise SystemExit(f"Indices non continus dans le verrou: {actual!r}")
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def verify_head(repository, expected_commit, app_name):
|
||||
actual = command(
|
||||
["git", "rev-parse", "HEAD"],
|
||||
cwd=repository,
|
||||
capture=True,
|
||||
)
|
||||
|
||||
if actual != expected_commit:
|
||||
raise SystemExit(
|
||||
f"Commit incorrect pour {app_name}: "
|
||||
f"attendu={expected_commit} obtenu={actual}"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Clone les applications Frappe aux commits du verrou."
|
||||
)
|
||||
parser.add_argument("lock_file", type=Path)
|
||||
parser.add_argument("bench_path", type=Path)
|
||||
parser.add_argument("--validate-only", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
entries = load_lock(args.lock_file)
|
||||
app_entries = [
|
||||
(name, metadata)
|
||||
for name, metadata in entries
|
||||
if name != "frappe"
|
||||
]
|
||||
|
||||
if args.validate_only:
|
||||
print(
|
||||
f"Verrou valide: {len(entries)} entrées, "
|
||||
f"{len(app_entries)} applications."
|
||||
)
|
||||
return
|
||||
|
||||
bench = args.bench_path.resolve()
|
||||
apps_dir = bench / "apps"
|
||||
sites_dir = bench / "sites"
|
||||
|
||||
frappe_metadata = dict(entries)["frappe"]
|
||||
verify_head(
|
||||
apps_dir / "frappe",
|
||||
frappe_metadata["resolution"]["commit_hash"],
|
||||
"frappe",
|
||||
)
|
||||
|
||||
for name, metadata in app_entries:
|
||||
target = apps_dir / name
|
||||
|
||||
if target.exists():
|
||||
raise SystemExit(f"Répertoire déjà présent: {target}")
|
||||
|
||||
resolution = metadata["resolution"]
|
||||
branch = resolution["branch"]
|
||||
commit = resolution["commit_hash"]
|
||||
url = metadata["url"]
|
||||
|
||||
print(f"Clonage de {name} @ {commit}")
|
||||
|
||||
command(
|
||||
[
|
||||
"git",
|
||||
"clone",
|
||||
"--filter=blob:none",
|
||||
"--no-checkout",
|
||||
"--single-branch",
|
||||
"--branch",
|
||||
branch,
|
||||
url,
|
||||
str(target),
|
||||
]
|
||||
)
|
||||
command(
|
||||
["git", "checkout", "--detach", commit],
|
||||
cwd=target,
|
||||
)
|
||||
verify_head(target, commit, name)
|
||||
|
||||
app_names = [name for name, _ in entries]
|
||||
(sites_dir / "apps.txt").write_text(
|
||||
"\n".join(app_names) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
print(f"Applications verrouillées clonées: {len(app_entries)}")
|
||||
print("Frappe et applications vérifiés: OK")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user