本文目录导读:

在Python中生成Chef配置,可以通过以下几种方式实现:
使用模板引擎生成Chef资源文件
使用Jinja2模板
from jinja2 import Template
# 定义模板
template_str = """
package '{{ package_name }}' do
action :install
end
template '/etc/{{ config_name }}.conf' do
source '{{ template_name }}.erb'
owner 'root'
group 'root'
mode '0644'
variables(
:port => {{ port }},
:host => '{{ host }}'
)
end
service '{{ service_name }}' do
supports :status => true, :restart => true
action [:enable, :start]
end
"""
# 创建模板对象
template = Template(template_str)
# 填充数据
config = {
'package_name': 'nginx',
'config_name': 'nginx',
'template_name': 'nginx',
'port': 8080,
'host': 'localhost',
'service_name': 'nginx'
}
# 渲染配置
chef_config = template.render(config)
# 写入文件
with open('recipes/default.rb', 'w') as f:
f.write(chef_config)
print(chef_config)
使用Python内置字符串格式化
def generate_chef_config(service_name, package_name, config_values):
"""生成Chef配置"""
config_template = """
# Cookbook: {service_name}
# Recipe: default
package '{package_name}' do
action :install
version '{version}'
end
service '{service_name}' do
supports :status => true, :restart => true
action [:enable, :start]
end
# 配置文件管理
directory '/etc/{service_name}' do
owner 'root'
group 'root'
mode '0755'
recursive true
action :create
end
template '/etc/{service_name}/{service_name}.conf' do
source '{service_name}.erb'
owner 'root'
group 'root'
mode '0644'
variables(
:port => {port},
:host => '{host}',
:max_clients => {max_clients}
)
end
"""
return config_template.format(
service_name=service_name,
package_name=package_name,
version=config_values.get('version', 'latest'),
port=config_values.get('port', 80),
host=config_values.get('host', 'localhost'),
max_clients=config_values.get('max_clients', 100)
)
# 使用示例
config = {
'version': '1.18.0',
'port': 8080,
'host': 'web.example.com',
'max_clients': 500
}
chef_recipe = generate_chef_config('nginx', 'nginx', config)
print(chef_recipe)
# 保存到文件
with open('nginx_recipe.rb', 'w') as f:
f.write(chef_recipe)
使用自定义类生成复杂配置
class ChefConfigGenerator:
"""Chef配置生成器"""
def __init__(self, cookbook_name):
self.cookbook_name = cookbook_name
self.resources = []
def add_package(self, name, version=None, action='install'):
"""添加包资源"""
self.resources.append({
'type': 'package',
'name': name,
'version': version,
'action': action
})
def add_service(self, name, action=['enable', 'start'], supports=None):
"""添加服务资源"""
if supports is None:
supports = {'status': True, 'restart': True}
self.resources.append({
'type': 'service',
'name': name,
'action': action,
'supports': supports
})
def add_template(self, name, source, variables=None,
owner='root', group='root', mode='0644'):
"""添加模板资源"""
if variables is None:
variables = {}
self.resources.append({
'type': 'template',
'name': name,
'source': source,
'owner': owner,
'group': group,
'mode': mode,
'variables': variables
})
def add_directory(self, name, owner='root', group='root',
mode='0755', recursive=True):
"""添加目录资源"""
self.resources.append({
'type': 'directory',
'name': name,
'owner': owner,
'group': group,
'mode': mode,
'recursive': recursive
})
def generate_resource_code(self, resource):
"""生成资源代码"""
type_map = {
'package': self._generate_package,
'service': self._generate_service,
'template': self._generate_template,
'directory': self._generate_directory
}
generator = type_map.get(resource['type'])
if generator:
return generator(resource)
return ''
def _generate_package(self, resource):
code = f"package '{resource['name']}' do\n"
if resource['version']:
code += f" version '{resource['version']}'\n"
code += f" action :{resource['action']}\nend\n"
return code
def _generate_service(self, resource):
code = f"service '{resource['name']}' do\n"
supports = resource['supports']
code += f" supports :status => {supports.get('status', True)}, :restart => {supports.get('restart', True)}\n"
action_list = resource['action']
code += f" action [:{', :'.join(action_list)}]\nend\n"
return code
def _generate_template(self, resource):
code = f"template '{resource['name']}' do\n"
code += f" source '{resource['source']}'\n"
code += f" owner '{resource['owner']}'\n"
code += f" group '{resource['group']}'\n"
code += f" mode '{resource['mode']}'\n"
if resource['variables']:
code += " variables(\n"
for key, value in resource['variables'].items():
if isinstance(value, str):
code += f" :{key} => '{value}',\n"
else:
code += f" :{key} => {value},\n"
code += " )\n"
code += "end\n"
return code
def _generate_directory(self, resource):
code = f"directory '{resource['name']}' do\n"
code += f" owner '{resource['owner']}'\n"
code += f" group '{resource['group']}'\n"
code += f" mode '{resource['mode']}'\n"
if resource['recursive']:
code += " recursive true\n"
code += " action :create\nend\n"
return code
def generate_recipe(self):
"""生成完整食谱"""
header = f"# Cookbook: {self.cookbook_name}\n# Recipe: default\n\n"
body = "\n".join(self.generate_resource_code(r) for r in self.resources)
return header + body
# 使用示例
generator = ChefConfigGenerator('my_app')
generator.add_package('nginx', version='1.18.0')
generator.add_directory('/etc/nginx')
generator.add_template(
'/etc/nginx/nginx.conf',
'nginx.erb',
variables={'port': 8080, 'host': 'localhost'}
)
generator.add_service('nginx')
recipe = generator.generate_recipe()
print(recipe)
# 保存到文件
with open('recipes/default.rb', 'w') as f:
f.write(recipe)
从YAML/JSON配置生成Chef配置
import yaml
import json
def generate_chef_from_yaml(yaml_file):
"""从YAML文件生成Chef配置"""
with open(yaml_file, 'r') as f:
config = yaml.safe_load(f)
chef_code = []
chef_code.append(f"# Generated Chef Cookbook: {config.get('cookbook_name', 'default')}")
# 处理包安装
for package in config.get('packages', []):
chef_code.append(f"package '{package['name']}' do")
if 'version' in package:
chef_code.append(f" version '{package['version']}'")
chef_code.append(" action :install")
chef_code.append("end\n")
# 处理服务
for service in config.get('services', []):
chef_code.append(f"service '{service['name']}' do")
chef_code.append(" action [:enable, :start]")
chef_code.append("end\n")
# 处理模板
for template in config.get('templates', []):
chef_code.append(f"template '{template['path']}' do")
chef_code.append(f" source '{template['source']}'")
chef_code.append(f" owner '{template.get('owner', 'root')}'")
chef_code.append(f" group '{template.get('group', 'root')}'")
chef_code.append(f" mode '{template.get('mode', '0644')}'")
if 'variables' in template:
chef_code.append(" variables(")
for key, value in template['variables'].items():
chef_code.append(f" :{key} => '{value}',")
chef_code.append(" )")
chef_code.append("end\n")
return "\n".join(chef_code)
# YAML配置示例
yaml_config = """
cookbook_name: web_server
packages:
- name: nginx
version: 1.18.0
services:
- name: nginx
templates:
- path: /etc/nginx/nginx.conf
source: nginx.erb
owner: root
group: root
mode: '0644'
variables:
port: 8080
host: localhost
"""
# 使用示例
with open('config.yaml', 'w') as f:
f.write(yaml_config)
chef_config = generate_chef_from_yaml('config.yaml')
print(chef_config)
完整的自动化生成脚本示例
#!/usr/bin/env python3
"""
Chef配置一键生成工具
支持生成完整的Cookbook结构
"""
import os
import json
from pathlib import Path
class ChefCookbookGenerator:
def __init__(self, cookbook_name, cookbook_path='.'):
self.cookbook_name = cookbook_name
self.cookbook_path = Path(cookbook_path) / cookbook_name
def create_cookbook_structure(self):
"""创建Cookbook目录结构"""
directories = [
'recipes',
'templates/default',
'attributes',
'files/default',
'metadata.rb'
]
for directory in directories:
path = self.cookbook_path / directory
if '.' not in directory: # 不是文件
path.mkdir(parents=True, exist_ok=True)
def generate_metadata(self):
"""生成metadata.rb"""
metadata_content = f"""
name '{self.cookbook_name}'
maintainer 'Generated by Python'
maintainer_email 'admin@example.com'
license 'Apache-2.0'
description 'Installs/Configures {self.cookbook_name}'
version '0.1.0'
supports 'centos'
supports 'ubuntu'
depends 'yum'
depends 'systemd'
"""
with open(self.cookbook_path / 'metadata.rb', 'w') as f:
f.write(metadata_content.strip())
def generate_default_recipe(self, packages, services, config_templates):
"""生成默认食谱"""
recipe_content = [
"#",
f"# Cookbook:: {self.cookbook_name}",
"# Recipe:: default",
"#",
"",
"# Update package list",
"execute 'update_package_list' do",
" command 'apt-get update'",
" action :nothing",
"end",
""
]
# 添加包安装
for package in packages:
recipe_content.append(f"package '{package}' do")
recipe_content.append(" action :install")
recipe_content.append(" notifies :run, 'execute[update_package_list]', :immediately")
recipe_content.append("end")
recipe_content.append("")
# 添加配置文件
for template_name, template_config in config_templates.items():
recipe_content.append(f"template '{template_config['path']}' do")
recipe_content.append(f" source '{template_name}.erb'")
recipe_content.append(f" owner '{template_config.get('owner', 'root')}'")
recipe_content.append(f" group '{template_config.get('group', 'root')}'")
recipe_content.append(f" mode '{template_config.get('mode', '0644')}'")
if 'variables' in template_config:
recipe_content.append(" variables(")
for key, value in template_config['variables'].items():
recipe_content.append(f" :{key} => '{value}',")
recipe_content.append(" )")
recipe_content.append("end")
recipe_content.append("")
# 添加服务
for service_name, service_config in services.items():
recipe_content.append(f"service '{service_name}' do")
recipe_content.append(" supports :status => true, :restart => true, :reload => true")
recipe_content.append(f" action [:{', :'.join(service_config.get('actions', ['enable', 'start']))}]")
if 'notifies' in service_config:
for notification in service_config['notifies']:
recipe_content.append(f" notifies :{notification['action']}, '{notification['resource']}'")
recipe_content.append("end")
recipe_content.append("")
# 写入文件
recipe_path = self.cookbook_path / 'recipes' / 'default.rb'
with open(recipe_path, 'w') as f:
f.write('\n'.join(recipe_content))
def generate(self, config):
"""根据配置生成完整的Cookbook"""
self.create_cookbook_structure()
self.generate_metadata()
self.generate_default_recipe(
config.get('packages', []),
config.get('services', {}),
config.get('templates', {})
)
# 使用示例
if __name__ == '__main__':
# 配置
config = {
'packages': ['nginx', 'php-fpm', 'mysql-server'],
'services': {
'nginx': {
'actions': ['enable', 'start'],
'notifies': [
{'action': 'reload', 'resource': 'service[php-fpm]'}
]
},
'php-fpm': {
'actions': ['enable', 'start']
},
'mysql': {
'actions': ['enable', 'start']
}
},
'templates': {
'nginx': {
'path': '/etc/nginx/nginx.conf',
'owner': 'root',
'group': 'root',
'mode': '0644',
'variables': {
'server_name': 'example.com',
'document_root': '/var/www/html',
'port': 80
}
},
'php': {
'path': '/etc/php/7.4/fpm/pool.d/www.conf',
'owner': 'root',
'group': 'root',
'mode': '0644',
'variables': {
'user': 'www-data',
'group': 'www-data',
'listen_port': 9000
}
}
}
}
# 生成Cookbook
generator = ChefCookbookGenerator('web_cookbook', './cookbooks')
generator.generate(config)
print("Chef Cookbook 生成完成!")
print(f"路径: ./cookbooks/web_cookbook")
使用建议
-
选择合适的工具:
- 简单配置:使用字符串格式化
- 复杂配置:使用Jinja2模板
- 需要代码逻辑:使用面向对象方式
-
模板管理:
- 将常用的配置模板保存为文件
- 支持变量替换和条件逻辑
-
错误处理:
def safe_generate_chef_config(config_data): """安全的配置生成函数""" try: # 验证配置数据 if not config_data.get('name'): raise ValueError("Cookbook name is required") # 生成配置 chef_code = generate_config(config_data) # 验证生成的代码语法 # (可以集成Chef验证工具) return chef_code except Exception as e: print(f"生成配置失败: {e}") return None -
集成测试:
def test_generated_config(): """测试生成的配置""" test_config = { 'package_name': 'test-package', 'service_name': 'test-service' } result = generate_chef_config(test_config) # 验证配置包含必要元素 assert 'package' in result assert 'service' in result assert 'end' in result print("测试通过!")
这种方法可以自动化生成大量Chef配置,特别适合管理多个环境或需要批量生成配置文件的情况。