export_docker_env.py 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. #!/usr/bin/env python3
  2. """将 dotenv 转成 docker run --env-file 的字面值格式,不打印凭证。"""
  3. import argparse
  4. import os
  5. from pathlib import Path
  6. import re
  7. import tempfile
  8. from dotenv import dotenv_values
  9. def export(source, target):
  10. source, target = Path(source).resolve(strict=True), Path(target).absolute()
  11. if source == target.resolve():
  12. raise ValueError('导出文件不能覆盖原 .env')
  13. rows = []
  14. for key, value in dotenv_values(source).items():
  15. if not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', key):
  16. raise ValueError('存在 Docker 不支持的环境变量名称')
  17. if value is None:
  18. continue
  19. if any(char in value for char in '\r\n\x00'):
  20. raise ValueError(f'{key} 含有 Docker env-file 不支持的换行或空字符')
  21. rows.append(key + '=' + value + '\n')
  22. target.parent.mkdir(parents=True, exist_ok=True)
  23. partial = None
  24. try:
  25. # 临时文件权限0600;写完后原子替换,保留原 .env,不输出任何配置值。
  26. with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', dir=target.parent,
  27. prefix='.docker-env-', delete=False) as stream:
  28. partial = Path(stream.name)
  29. stream.writelines(rows)
  30. os.replace(partial, target)
  31. finally:
  32. if partial is not None:
  33. partial.unlink(missing_ok=True)
  34. return len(rows)
  35. if __name__ == '__main__':
  36. parser = argparse.ArgumentParser(description=__doc__)
  37. parser.add_argument('source', nargs='?', default='.env')
  38. parser.add_argument('target', nargs='?', default='.env.docker')
  39. args = parser.parse_args()
  40. try:
  41. count = export(args.source, args.target)
  42. except (OSError, ValueError) as exc:
  43. parser.exit(1, f'转换失败:{exc}\n')
  44. print(f'已导出 {count} 个配置项,文件权限0600;未输出配置值。')