merge(codex/split-modules): split modules into alicloud/ and aws/

This commit is contained in:
Haitao Pan 2025-09-26 15:17:46 +08:00
commit c890f0e83f
23 changed files with 282 additions and 0 deletions

View File

@ -0,0 +1,97 @@
import os
import pulumi
import pulumi_aws as aws
from .utils import resolve_ami
def create_instances(instances_config, subnets_dict, sg_map: dict, key_name, depends_on=None):
outputs = {}
for instance_cfg in instances_config:
name = instance_cfg["name"]
subnet_name = instance_cfg["subnet"]
subnet = subnets_dict[subnet_name]
subnet_id = subnet.id
region = aws.config.region
ami = resolve_ami(instance_cfg["ami"], region)
instance_type = instance_cfg["type"]
disk_size = instance_cfg["disk_size_gb"]
lifecycle = instance_cfg.get("lifecycle", "ondemand")
ttl = instance_cfg.get("ttl", "none")
env = instance_cfg.get("env", "dev")
owner = instance_cfg.get("owner", "unknown")
user_data_path = instance_cfg.get("user_data")
private_ip = instance_cfg.get("private_ip", None)
associate_public_ip = instance_cfg.get("associate_public_ip", True)
# ✅ User data
user_data = None
if user_data_path:
expanded_path = os.path.expanduser(user_data_path)
if os.path.exists(expanded_path):
with open(expanded_path, "r") as f:
user_data = f.read()
else:
pulumi.log.warn(f"⚠️ user_data 文件不存在: {expanded_path}")
tags = {
"Name": name,
"Lifecycle": lifecycle,
"TTL": ttl,
"Environment": env,
"Owner": owner,
}
# ✅ Spot 实例配置
instance_market_options = None
if lifecycle == "spot":
instance_market_options = aws.ec2.InstanceInstanceMarketOptionsArgs(
market_type="spot",
spot_options=aws.ec2.InstanceInstanceMarketOptionsSpotOptionsArgs(
instance_interruption_behavior="terminate",
spot_instance_type="one-time"
)
)
# ✅ 解析 security group ids通过名字
sg_names = instance_cfg.get("sg_names", [])
security_group_ids = []
for sg_name in sg_names:
sg = sg_map.get(sg_name)
if sg:
security_group_ids.append(sg.id)
else:
pulumi.log.warn(f"⚠️ 实例 '{name}' 引用的 SG '{sg_name}' 未找到,已跳过")
# ✅ 构建依赖项
resource_dependencies = [subnet]
for sg in security_group_ids:
resource_dependencies.append(sg_map.get(sg_name))
if depends_on:
resource_dependencies.extend(depends_on)
# ✅ 创建实例
ec2 = aws.ec2.Instance(name,
ami=ami,
instance_type=instance_type,
key_name=key_name,
subnet_id=subnet_id,
private_ip=private_ip,
associate_public_ip_address=associate_public_ip,
vpc_security_group_ids=security_group_ids,
user_data=user_data,
root_block_device={
"volume_size": disk_size,
"volume_type": "gp2"
},
instance_market_options=instance_market_options,
tags=tags,
opts=pulumi.ResourceOptions(depends_on=resource_dependencies)
)
outputs[name + "_id"] = ec2.id
outputs[name + "_public_ip"] = ec2.public_ip
outputs[name + "_private_ip"] = ec2.private_ip
return outputs

View File

@ -0,0 +1,43 @@
import pulumi_aws as aws
AMI_MAP = {
"ubuntu-22.04": ("099720109477", "*ubuntu*22.04*"),
"ubuntu-24.04": ("099720109477", "*ubuntu*24.04*"),
"rocky-8.10": ("792107900819", "Rocky-8-ec2-8.10*"),
"amazonlinux-2": ("137112412989", "amzn2-ami-hvm-*-gp2"),
"amazonlinux-2023": ("137112412989", "al2023-ami-*-x86_64"),
"debian-12": ("136693071363", "debian-12-*"),
"almalinux-9": ("151447241410", "AlmaLinux-9-*"),
}
def query_latest_ami(owner: str, name_filter: str, architecture: str = "x86_64") -> str:
result = aws.ec2.get_ami(
most_recent=True,
owners=[owner],
filters=[
{"name": "name", "values": [name_filter]},
{"name": "architecture", "values": [architecture]},
{"name": "virtualization-type", "values": ["hvm"]},
],
)
return result.id
def resolve_ami(ami_keyword: str, region: str, architecture: str = "x86_64") -> str:
if not aws.config.region:
raise ValueError("❌ AWS region is not set. Please set aws.config.region before calling resolve_ami")
if ami_keyword.startswith("ami-"):
return ami_keyword
keyword = ami_keyword.lower()
print(f"🔍 Resolving AMI for keyword='{keyword}' in region='{region}' with arch='{architecture}'")
if keyword in AMI_MAP:
owner, name_filter = AMI_MAP[keyword]
try:
return query_latest_ami(owner, name_filter, architecture)
except Exception as e:
raise ValueError(f"❌ Failed to find AMI for '{keyword}' in region '{region}': {e}")
raise ValueError(f"❌ Unsupported AMI keyword: {ami_keyword}. Supported keywords: {list(AMI_MAP.keys())}")

View File

@ -0,0 +1,66 @@
import pulumi_aws as aws
from pulumi_aws.ec2 import SecurityGroup, SecurityGroupIngressArgs, SecurityGroupEgressArgs
def create_security_group(vpc_id: str, rule_config: dict) -> SecurityGroup:
"""
创建 Security Group支持 ingress/egress 配置包括 TCP, UDP, ICMP
:param vpc_id: 目标 VPC ID
:param rule_config: 单个 firewall_rules 的字典配置
:return: 创建的 SecurityGroup 资源对象
"""
ingress_rules = []
source_ranges = rule_config.get("source_ranges", ["0.0.0.0/0"])
egress_ranges = rule_config.get("egress_ranges", ["0.0.0.0/0"])
for allow_rule in rule_config.get("allow", []):
protocol = allow_rule.get("protocol", "tcp").lower()
ports = allow_rule.get("ports", [])
# ICMP 无需端口处理
if protocol == "icmp":
ingress_rules.append(
SecurityGroupIngressArgs(
protocol="icmp",
from_port=-1,
to_port=-1,
cidr_blocks=source_ranges
)
)
continue
# 处理 TCP/UDP 等需要端口的协议
for port in ports:
if isinstance(port, str) and port.lower() in ["*", "any", "all"]:
from_port, to_port = 0, 65535
else:
port = int(port)
from_port = to_port = port
ingress_rules.append(
SecurityGroupIngressArgs(
protocol=protocol,
from_port=from_port,
to_port=to_port,
cidr_blocks=source_ranges
)
)
# 创建 Security Group
sg = aws.ec2.SecurityGroup(
rule_config.get("name", "default-sg"),
vpc_id=vpc_id,
description=f"Security Group: {rule_config.get('name', 'N/A')}",
ingress=ingress_rules,
egress=[
SecurityGroupEgressArgs(
protocol="-1",
from_port=0,
to_port=0,
cidr_blocks=egress_ranges
)
],
tags={"Name": rule_config.get("name", "default-sg")}
)
return sg

View File

@ -0,0 +1,76 @@
import pulumi_aws as aws
import pulumi
def create_vpcs(vpc_list, region):
results = {}
for vpc_conf in vpc_list:
result = create_vpc(vpc_conf, region)
results[vpc_conf["name"]] = result
return results
def create_vpc(vpc_conf, region):
vpc = aws.ec2.Vpc(vpc_conf['name'],
cidr_block=vpc_conf['cidr_block'],
enable_dns_support=True,
enable_dns_hostnames=True,
tags={"Name": vpc_conf['name']}
)
# 判断是否包含公有子网
has_public = any(subnet["type"] == "public" for subnet in vpc_conf["subnets"])
igw = aws.ec2.InternetGateway(f"{vpc_conf['name']}-igw", vpc_id=vpc.id) if has_public else None
subnets = {}
for subnet_cfg in vpc_conf["subnets"]:
subnet = aws.ec2.Subnet(subnet_cfg["name"],
vpc_id=vpc.id,
cidr_block=subnet_cfg["cidr_block"],
map_public_ip_on_launch=subnet_cfg["type"] == "public",
availability_zone=subnet_cfg["availability_zone"],
tags={"Name": subnet_cfg["name"]}
)
subnets[subnet_cfg["name"]] = subnet
# 路由表创建,根据 subnet_type 分组
route_tables = {}
if "routes" in vpc_conf:
for route_cfg in vpc_conf["routes"]:
subnet_type = route_cfg["subnet_type"]
route_table_name = f"{vpc_conf['name']}-{subnet_type}-rt"
# 如果还未创建该类型的路由表,则创建
if subnet_type not in route_tables:
route_table = aws.ec2.RouteTable(route_table_name,
vpc_id=vpc.id,
routes=[],
tags={"Name": route_table_name}
)
route_tables[subnet_type] = route_table
else:
route_table = route_tables[subnet_type]
# 添加路由条目(追加)
aws.ec2.Route(f"{route_table_name}-{route_cfg['destination_cidr_block'].replace('/', '-')}",
route_table_id=route_table.id,
destination_cidr_block=route_cfg["destination_cidr_block"],
gateway_id=igw.id if route_cfg["gateway"] == "internet_gateway" else None
)
# 路由表关联到子网
for subnet_cfg in vpc_conf["subnets"]:
subnet_type = subnet_cfg["type"]
if subnet_type in route_tables:
aws.ec2.RouteTableAssociation(f"{subnet_cfg['name']}-assoc",
subnet_id=subnets[subnet_cfg["name"]].id,
route_table_id=route_tables[subnet_type].id
)
# TODO: Peering 支持
return {
"vpc": vpc,
"subnets": subnets,
"igw": igw,
"route_tables": route_tables
}