feat: 初始化项目结构和基本功能
- 创建项目目录结构和主要文件 - 实现 Docker 和 Nginx 配置生成的基本功能 - 添加命令行参数解析和默认行为逻辑 - 实现配置文件读取和解析功能 - 添加 Jinja2 模板渲染功能 - 创建 Nginx 和 Docker Compose 配置模板
This commit is contained in:
commit
3b9dac3b7f
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
*
|
||||
!src/
|
||||
!src/*
|
||||
!.gitignore
|
||||
!default*
|
||||
!main.py
|
6
defaultenv
Normal file
6
defaultenv
Normal 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
68
main.py
Normal 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
115
src/config_reader.py
Normal 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
55
src/docker-compose.j2
Normal 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
13
src/jinja2_render.py
Normal 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
28
src/nginx-conf.j2
Normal 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 %}
|
Loading…
x
Reference in New Issue
Block a user