Boilerplates CLI
Boilerplates是一个用于管理基础设施模板(boilerplates)的复杂集合,并配备了一个Python CLI工具。它支持Terraform、Docker、Ansible、Kubernetes等多种技术,帮助您快速生成、定制和部署配置模板。
功能特性
- 多技术模板支持:提供Docker Compose、Terraform、Ansible、Kubernetes、Packer等基础设施模板。
- 交互式变量收集:通过交互式CLI提示,引导用户为模板变量输入值。
- 智能默认值管理:支持保存常用变量的默认值,并在多个项目中复用。
- Git仓库集成:模板库基于Git管理,支持添加、更新和移除自定义模板仓库。
- Jinja2模板渲染:使用Jinja2引擎渲染模板,支持条件语句和变量替换。
- 模块化架构:通过模块系统(Module)组织不同技术的模板和命令。
- 配置文件管理:提供配置管理功能,存储用户默认值和偏好设置。
- 安全与验证:包含模板语法验证、变量类型检查和路径安全防护。
安装指南
使用安装脚本(推荐)
通过自动化安装脚本安装Boilerplates CLI:
# 安装最新版本curl-fsSL https://raw.githubusercontent.com/christianlempa/boilerplates/main/scripts/install.sh|bash# 安装特定版本curl-fsSL https://raw.githubusercontent.com/christianlempa/boilerplates/main/scripts/install.sh|bash-s -- --version v1.2.3安装脚本使用pipx为CLI工具创建一个隔离环境。安装完成后,终端中即可使用boilerplates命令。
依赖要求
- Python 3+
- Git(用于模板库管理)
- pipx(用于隔离安装,安装脚本会自动检查)
使用说明
基础命令
# 查看帮助信息boilerplates --help# 查看版本boilerplates --version# 更新模板库boilerplates repo update# 列出所有已配置的库boilerplates repo list模板操作
# 列出所有可用的Docker Compose模板boilerplates compose list# 查看特定模板的详细信息boilerplates compose show nginx# 生成模板(交互式模式)boilerplates compose generate authentik# 生成模板到自定义输出目录boilerplates compose generate nginx my-nginx-server# 非交互式模式,直接指定变量值boilerplates compose generate traefik my-proxy\--varservice_name=traefik\--vartraefik_enabled=true\--vartraefik_host=proxy.example.com\--no-interactive管理默认值
# 设置变量的默认值boilerplates compose defaultssetcontainer_timezone"America/New_York"boilerplates compose defaultssetrestart_policy"unless-stopped"模板库管理
# 添加自定义模板库boilerplates repoaddmy-templates https://github.com/user/templates\--directory library\--branch main# 移除模板库boilerplates repo remove my-templates核心代码
CLI主入口 (cli/__main__.py)
#!/usr/bin/env python3""" Main entry point for the Boilerplates CLI application. This file serves as the primary executable when running the CLI. """from__future__importannotationsimportimportlibimportloggingimportpkgutilimportsysfrompathlibimportPathfromtypingimportOptionalfromtyperimportTyper,Context,Optionfromrich.consoleimportConsoleimportcli.modulesfromcli.core.registryimportregistryfromcli.coreimportrepo# Using standard Python exceptions instead of custom ones# NOTE: Placeholder version - will be overwritten by release script (.github/workflows/release.yaml)__version__="0.0.0"app=Typer(help="CLI tool for managing infrastructure boilerplates.\n\n[dim]Easily generate, customize, and deploy templates for Docker Compose, Terraform, Kubernetes, and more.\n\n [white]Made with 💜 by [bold]Christian Lempa[/bold]",add_completion=True,rich_markup_mode="rich",)console=Console()defsetup_logging(log_level:str="WARNING")->None:"""Configure the logging system with the specified log level. Args: log_level: The logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) Raises: ValueError: If the log level is invalid RuntimeError: If logging configuration fails """numeric_level=getattr(logging,log_level.upper(),None)ifnotisinstance(numeric_level,int):raiseValueError(f"Invalid log level '{log_level}'. Valid levels: DEBUG, INFO, WARNING, ERROR, CRITICAL")try:logging.basicConfig(level=numeric_level,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',datefmt='%Y-%m-%d %H:%M:%S')logger=logging.getLogger(__name__)logger.setLevel(numeric_level)exceptExceptionase:raiseRuntimeError(f"Failed to configure logging:{e}")@app.callback(invoke_without_command=True)defmain(version:Optional[bool]=Option(None,"--version","-v",help="Show the application version and exit",),log_level:str=Option("WARNING","--log-level","-l",help="Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",),ctx:Context=None,)->None:"""Main entry point for the boilerplates CLI. Args: version: Show version information log_level: Logging level for the application ctx: Typer context object """ifversion:console.print(f"Boilerplates CLI [bold]v{__version__}[/bold]")raiseSystemExit(0)try:setup_logging(log_level)except(ValueError,RuntimeError)ase:console_err.print(f"[red]Failed to set up logging:{e}[/red]")sys.exit(1)# Discover and register modules_discover_modules()# Register repository management commandsapp.add_typer(repo.app,name="repo")ifctx.invoked_subcommandisNone:console.print(app.info.help)raiseSystemExit(0)def_discover_modules()->None:"""Dynamically discover and register all modules in the modules package."""try:for_,name,_inpkgutil.iter_modules(cli.modules.__path__):module_path=f"cli.modules.{name}"try:importlib.import_module(module_path)logger.debug(f"Successfully imported module:{name}")exceptExceptionase:logger.warning(f"Failed to import module '{name}':{e}")continuelogger.info(f"Module discovery completed. Total modules:{len(list(registry.iter_module_classes()))}")exceptExceptionase:logger.error(f"Module discovery failed:{e}")raiseif__name__=="__main__":app()变量集合管理 (cli/core/collection.py)
from__future__importannotationsfromcollectionsimportdefaultdictfromtypingimportAny,Dict,List,Optional,Set,Unionimportloggingfrom.variableimportVariablefrom.sectionimportVariableSection logger=logging.getLogger(__name__)classVariableCollection:"""Manages variables grouped by sections and builds Jinja context."""def__init__(self,spec:dict[str,Any])->None:"""Initialize VariableCollection from a specification dictionary. Args: spec: Dictionary containing the complete variable specification structure Expected format (as used in compose.py): { "section_key": { "title": "Section Title", "prompt": "Optional prompt text", "toggle": "optional_toggle_var_name", "description": "Optional description", "vars": { "var_name": { "description": "Variable description", "type": "str", "default": "default_value", ... } } } } """ifnotisinstance(spec,dict):raiseValueError("Spec must be a dictionary")self._sections:Dict[str,VariableSection]={}# NOTE: The _variable_map provides a flat, O(1) lookup for any variable by its name,# avoiding the need to iterate through sections. It stores references to the same# Variable objects contained in the _set structure.self._variable_map:Dict[str,Variable]={}self._initialize_sections(spec)# Validate dependencies after all sections are loadedself._validate_dependencies()def_initialize_sections(self,spec:dict[str,Any])->None:"""Initialize sections from the spec."""forsection_key,section_datainspec.items():ifnotisinstance(section_data,dict):continue# Create VariableSectionsection_dict={"key":section_key,"title":section_data.get("title",section_key.title()),"description":section_data.get("description"),"toggle":section_data.get("toggle"),"required":section_data.get("required",section_key=="general"),}# Handle dependenciesifneeds:=section_data.get("needs"):section_dict["needs"]=needs section=VariableSection(section_dict)# Add variables to sectionif"vars"insection_dataandisinstance(section_data["vars"],dict):forvar_name,var_datainsection_data["vars"].items():ifnotisinstance(var_data,dict):continue# Create Variablevar_dict=var_data.copy()var_dict["name"]=var_name var_dict["origin"]="template"variable=Variable(var_dict)# Store in section and flat mapsection.variables[var_name]=variable self._variable_map[var_name]=variable self._sections[section_key]=sectiondef_validate_dependencies(self)->None:"""Validate that all section dependencies exist."""forsection_key,sectioninself._sections.items():fordepinsection.needs:ifdepnotinself._sections:logger.warning(f"Section '{section_key}' depends on non-existent section '{dep}'")defget_section(self,section_key:str)->Optional[VariableSection]:"""Get a section by its key."""returnself._sections.get(section_key)defget_sections(self)->Dict[str,VariableSection]:"""Get all sections."""returnself._sections.copy()defis_section_satisfied(self,section_key:str)->bool:"""Check if a section's dependencies are satisfied."""section=self._sections.get(section_key)ifnotsection:returnFalse# General section is always satisfiedifsection_key=="general":returnTrue# Check all dependenciesfordepinsection.needs:dep_section=self._sections.get(dep)ifnotdep_sectionornotdep_section.is_enabled():returnFalsereturnTruedefget_jinja_context(self)->Dict[str,Any]:"""Build Jinja2 template contextfromallvariables.Returns:Dictionary mapping variable names to their valuesfor更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手) 对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)