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