feat: 初始化项目结构和基本功能

- 创建项目目录结构和主要文件
- 实现 Docker 和 Nginx 配置生成的基本功能
- 添加命令行参数解析和默认行为逻辑
- 实现配置文件读取和解析功能
- 添加 Jinja2 模板渲染功能
- 创建 Nginx 和 Docker Compose 配置模板
This commit is contained in:
ZZY 2024-11-11 13:31:20 +08:00
commit 3b9dac3b7f
7 changed files with 291 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*
!src/
!src/*
!.gitignore
!default*
!main.py

6
defaultenv Normal file
View File

@ -0,0 +1,6 @@
DOCKER_INIPATH = docker.ini
NGINX_INIPATH = nginx.ini
DOCKER_COMPOSE_OUTPATH = docker-compose.yml
INNER_NGINX_OUTPATH = innner-nginx.conf
OUTER_NGINX_OUTPATH = outer-nginx.conf

68
main.py Normal file
View File

@ -0,0 +1,68 @@
"""
this is the main file
"""
import argparse
import os
import subprocess
from pathlib import Path
from dotenv import load_dotenv
from jinja2_render import render_dataclass
from src.config_reader import nginx_config, docker_config, docker2nginx
load_dotenv()
def run_command(command):
try:
result = subprocess.run(command, shell=True, check=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(result.stdout.decode())
except subprocess.CalledProcessError as e:
print(f"Command failed with error: {e.stderr.decode()}")
def docker_compose_action():
config_path = Path(os.getenv('DOCKER_CONFIG_PATH', 'docker.ini'))
output_path = Path(os.getenv('DOCKER_COMPOSE_OUTPATH', 'docker-compose.conf'))
(auto_config, other) = docker_config(config_path)
render_dataclass(auto_config, Path('./src/docker-compose.j2'), output_path, other)
def inner_nginx_action():
config_path = Path(os.getenv('DOCKER_CONFIG_PATH', 'docker.ini'))
output_path = Path(os.getenv('INNER_NGINX_OUTPATH', 'inner-nginx.conf'))
(auto_config, other) = docker2nginx(config_path)
render_dataclass(auto_config, Path('./src/nginx-conf.j2'), output_path, other)
def outer_nginx_action():
config_path = Path(os.getenv('NGINX_CONFIG_PATH', 'docker.ini'))
output_path = Path(os.getenv('OUTER_NGINX_OUTPATH', 'outer-nginx.conf'))
(auto_config, other) = nginx_config(config_path)
render_dataclass(auto_config, Path('./src/nginx-conf.j2'), output_path, other)
def main():
parser = argparse.ArgumentParser(description="Automate Docker and Nginx configuration.")
parser.add_argument('-c', '--docker-compose', action='store_true',
help='Generate Docker Compose')
parser.add_argument('-i', '--inner-nginx', action='store_true',
help='Generate Inner Nginx (docker network)')
parser.add_argument('-o', '--outer-nginx', action='store_true',
help='Generate Outer Nginx (host machine)')
args = parser.parse_args()
if args.docker_compose:
docker_compose_action()
elif args.inner_nginx:
inner_nginx_action()
elif args.outer_nginx:
outer_nginx_action()
else:
# Default behavior: perform all actions
docker_compose_action()
inner_nginx_action()
outer_nginx_action()
if __name__ == "__main__":
main()

115
src/config_reader.py Normal file
View File

@ -0,0 +1,115 @@
import configparser
from dataclasses import dataclass
from typing import Optional
from pathlib import Path
def read_config(file_path: Path) -> configparser.ConfigParser:
"""读取INI配置文件"""
config = configparser.ConfigParser()
config.read(file_path, encoding='utf-8')
return config
def config_get(config: configparser.ConfigParser, section, key):
"""从配置文件中获取值,支持默认值"""
return config.get(section, key, fallback=config.get(config.default_section, key, fallback=''))
@dataclass
class NginxConfig:
name: str = "unknown"
host: str = "localhost"
port: int = 80
output_path: Optional[str] = None
tcp_nopush: str = "off"
sendfile: str = "off"
client_max_body_size: str = "100m"
proxy_connect_timeout: str = "60s"
proxy_send_timeout: str = "60s"
proxy_read_timeout: str = "60s"
web_socket_proxy: bool = False
def nginx_config(file_path: Path) -> tuple[list[NginxConfig], dict[str, str]]:
"""读取INI配置文件并解析为NginxConfig列表"""
config = read_config(file_path)
configs = []
g_section = config.default_section
def _config_get(name):
return config_get(config, g_section, name)
for section in config.sections():
g_section = section
configs.append(NginxConfig(
name = section,
host = _config_get('host'),
port = _config_get('port'),
web_socket_proxy = _config_get('web_socket_proxy') == 'True',
))
return (configs, {})
@dataclass
class DockerComposeConfig:
name: str
container_name: str
image: str
ports: Optional[list[str]] = None
volumes: Optional[list[str]] = None
environment: Optional[list[str]] = None
extra_hosts: Optional[list[str]] = None
export: Optional[str] = None
network: Optional[str] = None
def docker_config(file_path: Path) -> tuple[list[DockerComposeConfig], dict[str, str]]:
"""读取INI配置文件并解析为DockerComposeConfig列表"""
config = read_config(file_path)
configs = []
g_section = ''
def _config_get(name):
return config_get(config, g_section, name)
def _get_item(name) -> list[str]:
return [i.strip() for i in _config_get(name).split(',') if i.strip()]
network = set()
for section in config.sections():
g_section = section
configs.append(DockerComposeConfig(
name = section,
container_name = _config_get('container_name'),
image = _config_get('image'),
volumes = _get_item('volumes'),
ports = _get_item('ports'),
environment = _get_item('environment'),
extra_hosts = _get_item('extra_hosts'),
export = _config_get('export'),
network = _config_get('network'),
))
network.add(_config_get('network'))
return (configs, {'networks': list(network)})
def docker2nginx(file_path: Path) -> tuple[list[NginxConfig], dict[str, str]]:
"将 docker-compose 服务转换为 nginx 代理"
"""读取INI配置文件并解析为DockerComposeConfig列表"""
config = read_config(file_path)
configs = []
g_section = ''
def _config_get(name):
return config_get(config, g_section, name)
def _get_item(name) -> list[str]:
return [i.strip() for i in _config_get(name).split(',') if i.strip()]
network = set()
for section in config.sections():
if (section == 'nginx'):
continue
g_section = section
configs.append(NginxConfig(
name = config.get(section, 'nginx_name', fallback=section),
host = _config_get('container_name'),
port = _config_get('export'),
))
network.add(_config_get('network'))
return (configs, {'networks': list(network)})

55
src/docker-compose.j2 Normal file
View File

@ -0,0 +1,55 @@
# docker-compose.j2
version: '3'
services:
{%- for config in autoconfigs %}
{{ config.name }}:
container_name: {{ config.container_name }}
image: {{ config.image }}
deploy:
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
max_attempts: 3
{%- if config.ports %}
ports:
{%- for port in config.ports %}
- {{ port }}
{%- endfor %}
{%- endif %}
{%- if config.volumes %}
volumes:
{%- for volume in config.volumes %}
- {{ volume }}
{%- endfor %}
{%- endif %}
{%- if config.environment %}
environment:
{%- for env in config.environment %}
- {{ env }}
{%- endfor %}
{%- endif %}
{%- if config.export %}
expose:
- {{ config.export }}
{%- endif %}
{%- if config.extra_hosts %}
extra_hosts:
{%- for host in config.extra_hosts %}
- {{ host }}
{%- endfor %}
{%- endif %}
{%- if config.network %}
networks:
- {{ config.network }}
{%- endif %}
{% endfor %}
{%- if networks %}
networks:
{%- for network in networks %}
{{ network }}:
{%- endfor %}
{%- endif %}

13
src/jinja2_render.py Normal file
View File

@ -0,0 +1,13 @@
from pathlib import Path
from dataclasses import asdict, dataclass
from jinja2 import Environment, FileSystemLoader, Template
def render_dataclass(configs: list[dataclass], tmp_path: Path, out_path: Path, other = None) -> str:
"from dataclasses to jinja template"
env = Environment(loader=FileSystemLoader(str(tmp_path.parent),
encoding='utf-8'))
template: Template = env.get_template(str(tmp_path.name))
data = [asdict(config) for config in configs]
res = template.render({'autoconfigs': data}, **other)
out_path.write_text(res, encoding='utf-8')
return res

28
src/nginx-conf.j2 Normal file
View File

@ -0,0 +1,28 @@
{%- for config in autoconfigs %}
server {
listen 80;
listen 443 ssl;
server_name {{ config.name }};
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
tcp_nopush {{ config.tcp_nopush }};
sendfile {{ config.sendfile }};
client_max_body_size {{ config.client_max_body_size }};
location / {
proxy_pass http://{{ config.host }}:{{ config.port }};
proxy_connect_timeout {{ config.proxy_connect_timeout}};
proxy_send_timeout {{ config.proxy_send_timeout }};
proxy_read_timeout {{ config.proxy_read_timeout }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
{%- if config.web_socket_proxy %}
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
{%- endif %}
}
}
{% endfor %}