ftp_upload.py 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. """
  2. ftp_upload.py - 将本地部署目录通过 FTP 上传到服务器。
  3. 可被 artifacts.py 调用,也可在 pipeline.py 中独立使用。
  4. """
  5. import ftplib
  6. import os
  7. import logging
  8. from pathlib import Path
  9. from typing import Optional
  10. logger = logging.getLogger(__name__)
  11. class FTPUploadError(Exception):
  12. """FTP 上传异常"""
  13. pass
  14. def upload_directory(cfg, local_dir: str) -> None:
  15. """
  16. 将本地目录递归上传到 FTP 服务器。
  17. Args:
  18. cfg: PipelineConfig 对象,包含 FTP 连接信息与 ftp_root 路径。
  19. local_dir: 本地待上传目录路径(如 .../HORIZON/J6B/AD26IDV01/xxx/deploy_dir)
  20. """
  21. local_path = Path(local_dir)
  22. if not local_path.is_dir():
  23. raise FTPUploadError(f"Local upload directory not found: {local_dir}")
  24. # 目标 FTP 根路径:DB-年/FTP_ROOT,与原 shell 一致
  25. import datetime
  26. ftp_root = f"DB-{datetime.datetime.now().year}/{cfg.ftp_root}"
  27. logger.info("Connecting to FTP %s:%d", cfg.ftp_host, cfg.ftp_port)
  28. ftp = ftplib.FTP()
  29. try:
  30. ftp.connect(host=cfg.ftp_host, port=cfg.ftp_port, timeout=30)
  31. ftp.login(user=cfg.ftp_user, passwd=cfg.ftp_password)
  32. ftp.set_pasv(True) # 被动模式
  33. _mkdir_p(ftp, ftp_root) # 确保远端根目录存在
  34. _upload_recursive(ftp, local_path, ftp_root)
  35. logger.info("FTP upload completed successfully: %s -> %s", local_dir, ftp_root)
  36. except ftplib.all_errors as e:
  37. raise FTPUploadError(f"FTP upload failed: {e}") from e
  38. finally:
  39. try:
  40. ftp.quit()
  41. except Exception:
  42. pass
  43. def _mkdir_p(ftp: ftplib.FTP, remote_path: str) -> None:
  44. """递归创建远端目录,类似 mkdir -p"""
  45. dirs = remote_path.strip('/').split('/')
  46. current = ''
  47. for d in dirs:
  48. current += f"/{d}"
  49. try:
  50. ftp.cwd(current)
  51. except ftplib.error_perm:
  52. ftp.mkd(current)
  53. ftp.cwd(current)
  54. def _upload_recursive(ftp: ftplib.FTP, local_path: Path, remote_dir: str) -> None:
  55. """递归上传文件与子目录"""
  56. ftp.cwd(remote_dir)
  57. for item in local_path.iterdir():
  58. remote_full = f"{remote_dir}/{item.name}"
  59. if item.is_file():
  60. with open(item, 'rb') as f:
  61. ftp.storbinary(f'STOR {item.name}', f)
  62. logger.debug("Uploaded file: %s", remote_full)
  63. elif item.is_dir():
  64. _mkdir_p(ftp, remote_full)
  65. _upload_recursive(ftp, item, remote_full)
  66. else:
  67. logger.warning("Skipping non-regular file: %s", item)