safety-eval-service/docs/build-tools2/push_image.py

193 lines
6.3 KiB
Python
Raw Normal View History

2026-08-28 19:16:33 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修正版根据扁平 credentials.conf 打包并推送 safety-eval-service 镜像到张家口 ACR
- 读取扁平 key=value 格式MASTER_HOST / SSH_USER / SSH_PASSWD / ZJK_* / HZ_*
- jar 重命名为 start.jar 放入 build context start/target/匹配根 Dockerfile COPY
- 构建前登录杭州 ACR 以拉取基础镜像 pub/jdk:1.8.0_202
- 构建后登录张家口 ACR 并推送
- 推送后执行 docker pull + 容器内 jar 校验验证可拉取 & 包正常
用法:
python -u push_image.py --jar <jar> --app jjb-saas-safety-eval --version 77
"""
import argparse
import os
import sys
import time
from datetime import datetime
import paramiko
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
CONF = os.path.join(SCRIPT_DIR, "credentials.conf")
ROOT_DOCKERFILE = r"E:/works/projects/safety-eval-service/Dockerfile"
REMOTE_JAR_NAME = "start.jar" # 根 Dockerfile COPY 期望的名字
def load_conf(path):
conf = {}
with open(path, "r", encoding="utf-8") as f:
for line in f:
s = line.strip()
if not s or s.startswith("#") or "=" not in s:
continue
k, v = s.split("=", 1)
conf[k.strip()] = v.strip()
return conf
def ssh_exec(client, cmd, timeout=600):
print(f"> {cmd[:200]}")
stdin, stdout, stderr = client.exec_command(cmd, timeout=timeout)
out = stdout.read().decode("utf-8", "replace")
err = stderr.read().decode("utf-8", "replace")
rc = stdout.channel.recv_exit_status()
if out.strip():
print(out.rstrip())
if err.strip() and rc != 0:
print("STDERR:", err.rstrip())
print(f"[exit={rc}]\n")
return rc, out, err
def main():
p = argparse.ArgumentParser()
p.add_argument("--jar", required=True)
p.add_argument("--app", required=True)
p.add_argument("--version", required=True)
p.add_argument("--dockerfile", default=ROOT_DOCKERFILE)
args = p.parse_args()
c = load_conf(CONF)
MASTER = c["MASTER_HOST"]
SSH_USER = c["SSH_USER"]
SSH_PASSWD = c["SSH_PASSWD"]
ZJK_REGISTRY = c["ZJK_REGISTRY"]
ZJK_REPO = c["ZJK_REPO"]
ZJK_USER = c["ZJK_USER"]
ZJK_PASS = c["ZJK_PASS"]
HZ_REGISTRY = c["HZ_REGISTRY"]
HZ_USER = c["HZ_USER"]
HZ_PASS = c["HZ_PASS"]
date_tag = datetime.now().strftime("%Y%m%d")
tag = f"{args.app}-ota-{date_tag}-{args.version}"
full_image = f"{ZJK_REGISTRY}/{ZJK_REPO}:{tag}"
jar_local = args.jar
if not os.path.exists(jar_local):
print("ERROR jar not found:", jar_local)
sys.exit(1)
build_dir = f"/tmp/docker-build-{args.app}"
print("=" * 64)
print(" GBS build & push -> 张家口 ACR (修正版)")
print("=" * 64)
print(f" JAR : {jar_local} ({os.path.getsize(jar_local)/1024/1024:.1f} MB)")
print(f" TAG : {tag}")
print(f" IMG : {full_image}")
print(f" HOST: {SSH_USER}@{MASTER}")
print("=" * 64)
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
MASTER,
username=SSH_USER,
password=SSH_PASSWD,
timeout=30,
look_for_keys=False,
allow_agent=False,
banner_timeout=30,
auth_timeout=30,
)
print("[1/6] SSH connected")
ssh_exec(client, f"rm -rf {build_dir} && mkdir -p {build_dir}/start/target")
print("[2/6] Upload JAR -> start/target/start.jar")
sftp = client.open_sftp()
remote_jar = f"{build_dir}/start/target/{REMOTE_JAR_NAME}"
start = time.time()
def cb(trans, tot):
pct = int(trans * 100 / tot)
if pct % 20 == 0:
print(f" {pct}% {trans/1024/1024:.1f} MB")
# confirm=False: 跳过 put 结束时的 stat 确认(远端 OpenSSH sftp-server 对刚写完的文件 stat 会返回 ENOENT
sftp.put(jar_local, remote_jar, callback=cb, confirm=False)
sftp.close()
print(f" done in {time.time()-start:.1f}s")
print("[3/6] Upload Dockerfile")
with open(args.dockerfile, "r", encoding="utf-8") as f:
df = f.read()
stdin, stdout, stderr = client.exec_command(f"cat > {build_dir}/Dockerfile")
stdin.write(df)
stdin.channel.shutdown_write()
stdout.read()
ssh_exec(client, f"ls -l {build_dir}/start/target/ {build_dir}/Dockerfile")
# 大小校验:确认远端 jar 与本地一致(绕过 sftp.stat 的已知问题,用 ssh ls
rc_ls, out_ls, _ = ssh_exec(client, f"stat -c %s {remote_jar}")
try:
remote_size = int(out_ls.strip().splitlines()[-1])
except Exception:
remote_size = -1
local_size = os.path.getsize(jar_local)
if remote_size != local_size:
print(f"ERROR: jar 大小不一致 local={local_size} remote={remote_size}")
client.close()
sys.exit(1)
print(f" jar size OK: {local_size} bytes")
print("[4/6] Login 杭州 ACR (基础镜像) & docker build")
ssh_exec(
client,
f"echo '{HZ_PASS}' | docker login --username={HZ_USER} --password-stdin {HZ_REGISTRY}",
)
rc, _, _ = ssh_exec(
client, f"cd {build_dir} && docker build -t '{full_image}' .", timeout=300
)
if rc != 0:
print("ERROR: docker build failed")
client.close()
sys.exit(1)
print("[5/6] Login 张家口 ACR & docker push")
ssh_exec(
client,
f"echo '{ZJK_PASS}' | docker login --username={ZJK_USER} --password-stdin {ZJK_REGISTRY}",
)
rc, _, _ = ssh_exec(client, f"docker push '{full_image}'", timeout=600)
if rc != 0:
print("ERROR: docker push failed")
client.close()
sys.exit(1)
print("[6/6] 验证:可拉取 + 包正常")
rc_pull, _, _ = ssh_exec(client, f"docker pull '{full_image}'", timeout=300)
ssh_exec(
client,
f"docker run --rm --entrypoint sh '{full_image}' -c "
f"'echo JAVA:; java -version 2>&1; echo JAR:; ls -l /opt/app.jar; "
f"unzip -t /opt/app.jar >/dev/null 2>&1 && echo JAR_OK || echo JAR_BAD'",
timeout=120,
)
ssh_exec(client, f"rm -rf {build_dir}")
client.close()
print("\n" + "=" * 64)
print(" DONE")
print("=" * 64)
print(f" Image: {full_image}")
print(f" Pullable: {'YES (docker pull exit=0)' if rc_pull == 0 else 'NO'}")
print("=" * 64)
if __name__ == "__main__":
main()