feat(iac): Refactor structure and support multi-environment config loading
- Add config/sit and other environment-specific config directories - Refactor deploy.py to support CONFIG_PATH environment variable - Enable automatic merging of config/*/*.yaml files - Enhance run.sh with Pulumi/Ansible/Terraform initialization checks - Add inventory.py to dynamically generate Ansible hosts - Improve ec2_instance.py with modular instance creation - Organize base.yaml, vpc.yaml and related config files"
This commit is contained in:
parent
cb57cb6782
commit
c2020da184
52
.gitmessage.txt
Normal file
52
.gitmessage.txt
Normal file
@ -0,0 +1,52 @@
|
||||
# 💡 提交说明模板 (Git Commit Message Template)
|
||||
#
|
||||
# 📌 标准格式:
|
||||
# <type>(<scope>): <简要描述>
|
||||
#
|
||||
# 📖 示例:
|
||||
# feat(iac): 新增支持多环境配置加载
|
||||
# feat(deploy): Add support for multi-environment config loading
|
||||
#
|
||||
# 可选结构:
|
||||
# - 中文描述(团队成员理解方便)
|
||||
# - 英文描述(CI/CD / PR 审阅更规范)
|
||||
#
|
||||
# 🧱 支持类型:
|
||||
# feat 💡 新功能 / Feature
|
||||
# fix 🐛 修复 bug / Bug fix
|
||||
# docs 📚 文档变更 / Documentation
|
||||
# style 🎨 代码格式 / Style only
|
||||
# refactor 🔨 重构 / Refactor (无功能变更)
|
||||
# perf 🚀 性能优化 / Performance
|
||||
# test 🧪 测试相关 / Add or update tests
|
||||
# chore 🔧 构建、工具、依赖更新 / Chores
|
||||
#
|
||||
# ⏱️ 每次提交只关注一类改动
|
||||
|
||||
# ---------------------- COMMIT MESSAGE START ----------------------
|
||||
|
||||
feat(iac): 重构目录结构并支持多环境配置加载
|
||||
feat(iac): Refactor structure and support multi-environment config loading
|
||||
|
||||
- 新增 config/sit 等多环境配置目录结构
|
||||
- Add config/sit and other environment-specific config directories
|
||||
|
||||
- 重构 deploy.py 适配 CONFIG_PATH 环境变量
|
||||
- Refactor deploy.py to support CONFIG_PATH environment variable
|
||||
|
||||
- 支持自动合并 config/*/*.yaml 配置
|
||||
- Enable automatic merging of config/*/*.yaml files
|
||||
|
||||
- 增强 run.sh 脚本,集成 Pulumi/Ansible/Terraform 初始化检查
|
||||
- Enhance run.sh with Pulumi/Ansible/Terraform initialization checks
|
||||
|
||||
- 新增 inventory.py 动态生成 Ansible 主机列表
|
||||
- Add inventory.py to dynamically generate Ansible hosts
|
||||
|
||||
- 整理 base.yaml、vpc.yaml 等配置文件
|
||||
- Organize base.yaml, vpc.yaml and related config files
|
||||
|
||||
# ----------------------- COMMIT MESSAGE END -----------------------
|
||||
|
||||
# 📝 注意:提交时只保留实际变更部分,其余注释会被 Git 忽略
|
||||
|
||||
32
README.md
32
README.md
@ -1,11 +1,41 @@
|
||||
# Modern Container Application Reference Architecture
|
||||
|
||||
Welcome to the repository for the Modern Container Application Reference Architecture. This repository contains a comprehensive guide and reference architecture for building scalable, portable, resilient, and agile containerized applications.
|
||||
Welcome to the repository for the Modern Container Application Reference Architecture. This repository contains a comprehensive guide and reference architecture for building scalable, portable, resilient, and agile containerized applications. 一个基于 Pulumi + Ansible 的基础设施自动化项目模板,支持多环境部署(dev / staging / prod),实现从基础设施创建到主机配置的全流程自动化管理。
|
||||
|
||||
---
|
||||
## Overview
|
||||
|
||||
The project aims to create a multi-cloud environment that leverages containers for deploying modern applications. The key objective is to set up a unified authentication system using **OIDC** via **Keycloak** for **AWS**, **GCP**, **Azure**, **GitHub**, **Harbor ** and **Grafana **.
|
||||
|
||||
## 🚀 项目功能
|
||||
|
||||
- 使用 Pulumi(Python)创建 AWS 基础设施(VPC、子网、安全组、EC2)
|
||||
- 配置结构模块化:`base.yaml`, `vpc.yaml`, `firewall.yaml`, `instances.yaml`
|
||||
- 支持 Spot / On-Demand 实例,支持 TTL 标签
|
||||
- 自动输出 EC2 IP,动态生成 Ansible Inventory
|
||||
- 使用 Ansible Playbook 远程安装软件或部署服务
|
||||
- 支持多环境 stack(dev/staging/prod)
|
||||
|
||||
## 项目结构
|
||||
|
||||
├── config/ # 多环境配置
|
||||
│ ├── base.yaml
|
||||
│ ├── vpc.yaml
|
||||
│ ├── firewall.yaml
|
||||
│ └── instances.yaml
|
||||
├── iac_modules/
|
||||
│ └── pulumi/
|
||||
│ ├── deploy.py # Pulumi 主入口
|
||||
│ ├── modules/ # VPC/SG/EC2 模块
|
||||
│ ├── utils/config_loader.py
|
||||
│ └── requirements.txt
|
||||
├── scripts/
|
||||
│ ├── infra.sh # 一键部署脚本
|
||||
│ └── inventory.py # 动态 Ansible inventory
|
||||
├── ansible/
|
||||
│ └── playbooks/
|
||||
│ └── setup.yml # 应用部署 playbook
|
||||
|
||||
## Phase 1: Implementing OIDC Login
|
||||
|
||||
In this first phase, we focus on implementing OpenID Connect (OIDC) login functionality for the following platforms:
|
||||
|
||||
7
config/sit/base.yaml
Normal file
7
config/sit/base.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
aws:
|
||||
access_key: YOUR_ACCESS_KEY
|
||||
secret_key: YOUR_SECRET_KEY
|
||||
region: us-east-1
|
||||
key_pairs:
|
||||
- name: dev_key
|
||||
key_file: keys/dev_ssh.pub
|
||||
6
config/sit/firewall.yaml
Normal file
6
config/sit/firewall.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
firewall_rules:
|
||||
- name: allow-ssh-web
|
||||
allow:
|
||||
- protocol: tcp
|
||||
ports: ["22", "80", "443"]
|
||||
source_ranges: ["0.0.0.0/0"]
|
||||
32
config/sit/instances.yaml
Normal file
32
config/sit/instances.yaml
Normal file
@ -0,0 +1,32 @@
|
||||
instances:
|
||||
- name: master-1
|
||||
ami: ami-0c2b8ca1dad447f8a
|
||||
type: t3.micro
|
||||
disk_size_gb: 20
|
||||
subnet: public-subnet-1
|
||||
lifecycle: spot # 可选: ondemand(默认)或 spot
|
||||
ttl: 1h # 可选: 自动标记 TTL,仅作为标识,不自动销毁
|
||||
|
||||
- name: slave-1
|
||||
ami: ami-0c2b8ca1dad447f8a
|
||||
type: t3.micro
|
||||
disk_size_gb: 20
|
||||
subnet: private-subnet-1
|
||||
lifecycle: spot
|
||||
ttl: 1h
|
||||
|
||||
- name: agent-1
|
||||
ami: ami-0c2b8ca1dad447f8a
|
||||
type: t3.micro
|
||||
disk_size_gb: 20
|
||||
subnet: private-subnet-1
|
||||
lifecycle: spot
|
||||
ttl: 1h
|
||||
|
||||
- name: agent-2
|
||||
ami: ami-0c2b8ca1dad447f8a
|
||||
type: t3.micro
|
||||
disk_size_gb: 20
|
||||
subnet: private-subnet-1
|
||||
lifecycle: spot
|
||||
ttl: 1h
|
||||
24
config/sit/vpc.yaml
Normal file
24
config/sit/vpc.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
vpc:
|
||||
name: dev-vpc
|
||||
cidr_block: 10.0.0.0/16
|
||||
subnets:
|
||||
- name: public-subnet-1
|
||||
cidr_block: 10.0.1.0/24
|
||||
availability_zone: us-east-1a
|
||||
type: public
|
||||
- name: private-subnet-1
|
||||
cidr_block: 10.0.101.0/24
|
||||
availability_zone: us-east-1a
|
||||
type: private
|
||||
|
||||
routes:
|
||||
- name: public-route
|
||||
destination_cidr_block: 0.0.0.0/0
|
||||
subnet_type: public
|
||||
gateway: internet_gateway
|
||||
|
||||
peering:
|
||||
enabled: false
|
||||
peer_vpc_id: null
|
||||
peer_region: null
|
||||
auto_accept: false
|
||||
63
iac_modules/pulumi/deploy.py
Normal file
63
iac_modules/pulumi/deploy.py
Normal file
@ -0,0 +1,63 @@
|
||||
import os
|
||||
import pulumi
|
||||
import pulumi_aws as aws
|
||||
from utils.config_loader import load_merged_config
|
||||
from modules.vpc.vpc import create_vpc
|
||||
from modules.security_group.sg import create_security_group
|
||||
from modules.ec2.ec2_instance import create_instances
|
||||
|
||||
# ✅ 自动从环境变量获取配置路径,默认为 "config/"
|
||||
config_dir = os.environ.get("CONFIG_PATH", "config")
|
||||
config = load_merged_config(config_dir)
|
||||
|
||||
# ✅ 提取配置项(如为空跳过)
|
||||
aws_conf = config.get("aws")
|
||||
vpc_conf = config.get("vpc")
|
||||
instances_conf = config.get("instances", [])
|
||||
firewall_rules = config.get("firewall_rules", [])
|
||||
|
||||
if not aws_conf or not vpc_conf:
|
||||
pulumi.log.warn(f"❌ 配置不完整,缺少 aws 或 vpc 段,终止部署。CONFIG_PATH={config_dir}")
|
||||
exit(0)
|
||||
|
||||
# ✅ 配置 AWS 凭据
|
||||
aws.config.region = aws_conf["region"]
|
||||
aws.config.access_key = aws_conf["access_key"]
|
||||
aws.config.secret_key = aws_conf["secret_key"]
|
||||
|
||||
# ✅ 创建 VPC 与子网
|
||||
vpc_result = create_vpc(vpc_conf, aws_conf["region"])
|
||||
vpc = vpc_result["vpc"]
|
||||
subnets = vpc_result["subnets"]
|
||||
|
||||
# ✅ 创建安全组(取第一组规则)
|
||||
if not firewall_rules:
|
||||
pulumi.log.warn("⚠️ 未定义 firewall_rules,默认跳过安全组配置")
|
||||
sg_id = None
|
||||
else:
|
||||
sg = create_security_group(vpc.id, firewall_rules[0])
|
||||
sg_id = sg.id
|
||||
|
||||
# ✅ SSH 密钥对
|
||||
key_cfg = aws_conf["key_pairs"][0]
|
||||
public_key_path = key_cfg["key_file"]
|
||||
if not os.path.exists(public_key_path):
|
||||
raise FileNotFoundError(f"❌ SSH 公钥文件不存在: {public_key_path}")
|
||||
with open(public_key_path, "r") as f:
|
||||
public_key = f.read().strip()
|
||||
|
||||
key_pair = aws.ec2.KeyPair("main-key",
|
||||
key_name=key_cfg["name"],
|
||||
public_key=public_key
|
||||
)
|
||||
|
||||
# ✅ 创建实例(自动匹配子网)
|
||||
if not instances_conf:
|
||||
pulumi.log.warn("⚠️ 未配置任何 EC2 实例,跳过实例部署")
|
||||
outputs = {}
|
||||
else:
|
||||
outputs = create_instances(instances_conf, subnets, sg_id, key_pair.key_name)
|
||||
|
||||
# ✅ 导出所有实例的公网 IP
|
||||
for name, ip in outputs.items():
|
||||
pulumi.export(f"{name}_ip", ip)
|
||||
54
iac_modules/pulumi/modules/ec2/ec2_instance.py
Normal file
54
iac_modules/pulumi/modules/ec2/ec2_instance.py
Normal file
@ -0,0 +1,54 @@
|
||||
import pulumi_aws as aws
|
||||
|
||||
def create_instances(instances_config, subnets_dict, sg_id, key_name):
|
||||
outputs = {}
|
||||
|
||||
for instance_cfg in instances_config:
|
||||
name = instance_cfg["name"]
|
||||
subnet_name = instance_cfg["subnet"]
|
||||
subnet_id = subnets_dict[subnet_name].id
|
||||
ami = instance_cfg["ami"]
|
||||
instance_type = instance_cfg["type"]
|
||||
disk_size = instance_cfg["disk_size_gb"]
|
||||
|
||||
# 读取可选字段
|
||||
lifecycle = instance_cfg.get("lifecycle", "ondemand") # 默认按需
|
||||
ttl = instance_cfg.get("ttl", "none") # 默认无 TTL
|
||||
|
||||
# 设置 EC2 标签
|
||||
tags = {
|
||||
"Name": name,
|
||||
"Lifecycle": lifecycle,
|
||||
"TTL": ttl,
|
||||
}
|
||||
|
||||
# 如果是 Spot 实例,设置市场选项(不设 max_price → 自动出价)
|
||||
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"
|
||||
)
|
||||
)
|
||||
|
||||
# 创建 EC2 实例
|
||||
ec2 = aws.ec2.Instance(name,
|
||||
ami=ami,
|
||||
instance_type=instance_type,
|
||||
key_name=key_name,
|
||||
subnet_id=subnet_id,
|
||||
vpc_security_group_ids=[sg_id],
|
||||
associate_public_ip_address=True,
|
||||
root_block_device={
|
||||
"volume_size": disk_size,
|
||||
"volume_type": "gp2"
|
||||
},
|
||||
instance_market_options=instance_market_options,
|
||||
tags=tags
|
||||
)
|
||||
|
||||
outputs[name] = ec2.public_ip
|
||||
|
||||
return outputs
|
||||
3
iac_modules/pulumi/requirements.txt
Normal file
3
iac_modules/pulumi/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
pulumi
|
||||
pulumi-aws
|
||||
PyYAML
|
||||
35
iac_modules/pulumi/utils/config_loader.py
Normal file
35
iac_modules/pulumi/utils/config_loader.py
Normal file
@ -0,0 +1,35 @@
|
||||
import os
|
||||
import glob
|
||||
import yaml
|
||||
from collections.abc import Mapping
|
||||
|
||||
def deep_merge(dict1, dict2):
|
||||
result = dict1.copy()
|
||||
for k, v in dict2.items():
|
||||
if k in result and isinstance(result[k], dict) and isinstance(v, Mapping):
|
||||
result[k] = deep_merge(result[k], v)
|
||||
elif k in result and isinstance(result[k], list) and isinstance(v, list):
|
||||
result[k] += v
|
||||
else:
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
def load_merged_config(config_dir=None):
|
||||
config_dir = config_dir or os.environ.get("CONFIG_PATH", "config")
|
||||
|
||||
if not os.path.isdir(config_dir):
|
||||
raise FileNotFoundError(f"❌ 配置目录不存在: {config_dir}")
|
||||
|
||||
merged = {}
|
||||
files = sorted(glob.glob(os.path.join(config_dir, "*.yaml")) + glob.glob(os.path.join(config_dir, "*.yml")))
|
||||
|
||||
if not files:
|
||||
raise FileNotFoundError(f"⚠️ 未找到任何 YAML 配置文件于: {config_dir}")
|
||||
|
||||
for file in files:
|
||||
with open(file) as f:
|
||||
part = yaml.safe_load(f) or {}
|
||||
merged = deep_merge(merged, part)
|
||||
|
||||
merged["__config_path__"] = config_dir # 可选调试字段
|
||||
return merged
|
||||
73
scripts/inventory.py
Normal file
73
scripts/inventory.py
Normal file
@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import yaml
|
||||
from collections import defaultdict
|
||||
|
||||
def get_pulumi_outputs():
|
||||
output = subprocess.check_output(["pulumi", "stack", "output", "--json"])
|
||||
return json.loads(output)
|
||||
|
||||
def merge_instance_config(config_dir="config"):
|
||||
merged = {}
|
||||
for fname in os.listdir(config_dir):
|
||||
if fname.endswith(".yaml"):
|
||||
with open(os.path.join(config_dir, fname)) as f:
|
||||
data = yaml.safe_load(f)
|
||||
if isinstance(data, dict):
|
||||
merged.update(data)
|
||||
return merged.get("instances", [])
|
||||
|
||||
def build_inventory(pulumi_outputs, instance_cfgs):
|
||||
inventory = {"_meta": {"hostvars": {}}}
|
||||
groups = defaultdict(list)
|
||||
|
||||
for inst in instance_cfgs:
|
||||
name = inst["name"]
|
||||
public_ip = pulumi_outputs.get(f"{name}_ip")
|
||||
|
||||
if not public_ip:
|
||||
continue # skip not created instances
|
||||
|
||||
# 默认分组:all
|
||||
groups["all"].append(name)
|
||||
|
||||
# 根据 subnet 或 lifecycle 添加分组
|
||||
if "subnet" in inst:
|
||||
groups[inst["subnet"]].append(name)
|
||||
if "lifecycle" in inst:
|
||||
groups[inst["lifecycle"]].append(name)
|
||||
|
||||
# hostvars
|
||||
inventory["_meta"]["hostvars"][name] = {
|
||||
"ansible_host": public_ip,
|
||||
"ansible_user": "ubuntu",
|
||||
"instance_type": inst.get("type"),
|
||||
"ttl": inst.get("ttl", "none"),
|
||||
"lifecycle": inst.get("lifecycle", "ondemand"),
|
||||
}
|
||||
|
||||
# 将分组注入 inventory
|
||||
for group, hosts in groups.items():
|
||||
inventory[group] = {"hosts": hosts}
|
||||
|
||||
return inventory
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--list', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list:
|
||||
pulumi_data = get_pulumi_outputs()
|
||||
instance_cfgs = merge_instance_config()
|
||||
inventory = build_inventory(pulumi_data, instance_cfgs)
|
||||
print(json.dumps(inventory, indent=2))
|
||||
else:
|
||||
print(json.dumps({}))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
207
scripts/run.sh
Normal file
207
scripts/run.sh
Normal file
@ -0,0 +1,207 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# 项目根目录(从任意位置运行都有效)
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# ========== 参数解析 ==========
|
||||
DEFAULT_ENV="dev"
|
||||
DEFAULT_CONFIG="config"
|
||||
|
||||
if [[ -n "$1" && "$1" != up && "$1" != down && "$1" != delete && "$1" != export && "$1" != import && "$1" != init && "$1" != ansible && "$1" != help ]]; then
|
||||
STACK_ENV="$1"
|
||||
ACTION="${2:-up}"
|
||||
else
|
||||
STACK_ENV="${STACK_ENV:-$DEFAULT_ENV}"
|
||||
ACTION="${1:-help}"
|
||||
fi
|
||||
|
||||
STACK_NAME="${STACK_NAME:-$STACK_ENV}"
|
||||
CONFIG_PATH="${CONFIG_PATH:-config/$STACK_ENV}"
|
||||
|
||||
# ========== 模块目录 ==========
|
||||
PULUMI_DIR="iac_modules/pulumi"
|
||||
TERRAFORM_DIR="iac_modules/terraform"
|
||||
ANSIBLE_DIR="ansible"
|
||||
|
||||
# ========== 帮助信息 ==========
|
||||
print_help() {
|
||||
|
||||
echo ""
|
||||
echo "🧰 iac_cli - 多环境自动化管理器 (IaC + Ansible + GitOps)"
|
||||
echo ""
|
||||
|
||||
echo "用法:"
|
||||
echo " ./scripts/run.sh [env] [命令]"
|
||||
echo " STACK_ENV=prod CONFIG_PATH=config/prod ./scripts/run.sh up"
|
||||
echo ""
|
||||
echo "🌍 当前环境: $STACK_ENV"
|
||||
echo "📁 当前配置路径: $CONFIG_PATH"
|
||||
echo ""
|
||||
echo "支持命令:"
|
||||
echo " up 🚀 部署资源"
|
||||
echo " down 🔥 销毁资源"
|
||||
echo " delete 🗑️ 删除 stack"
|
||||
echo " export 📤 导出 stack 状态"
|
||||
echo " import 📥 导入 stack 状态"
|
||||
echo " init ⚙️ 初始化依赖"
|
||||
echo " ansible 🧪 执行 ansible-playbook"
|
||||
echo " help 📖 显示帮助"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ========== 检查 Pulumi ==========
|
||||
ensure_pulumi() {
|
||||
if ! command -v pulumi &> /dev/null; then
|
||||
echo "📦 未检测到 Pulumi,正在自动安装..."
|
||||
case "$(uname | tr '[:upper:]' '[:lower:]')" in
|
||||
linux)
|
||||
curl -fsSL https://get.pulumi.com | sh
|
||||
export PATH="$HOME/.pulumi/bin:$PATH"
|
||||
;;
|
||||
darwin)
|
||||
brew install pulumi || (curl -fsSL https://get.pulumi.com | sh && export PATH="$HOME/.pulumi/bin:$PATH")
|
||||
;;
|
||||
msys*|mingw*|cygwin*)
|
||||
echo "👉 Windows 用户请手动安装 Pulumi:https://www.pulumi.com/docs/get-started/install/"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "❌ 当前平台不支持自动安装 Pulumi"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
echo "✅ Pulumi 版本: $(pulumi version)"
|
||||
}
|
||||
|
||||
# ========== 检查 Ansible ==========
|
||||
ensure_ansible() {
|
||||
if ! command -v ansible &> /dev/null; then
|
||||
echo "❌ 未检测到 Ansible,请手动安装:"
|
||||
case "$(uname | tr '[:upper:]' '[:lower:]')" in
|
||||
linux)
|
||||
echo "👉 Ubuntu/Debian: sudo apt install ansible"
|
||||
echo "👉 RHEL/CentOS: sudo yum install ansible"
|
||||
;;
|
||||
darwin)
|
||||
echo "👉 macOS: brew install ansible"
|
||||
;;
|
||||
msys*|mingw*|cygwin*)
|
||||
echo "👉 Windows 用户请参考官方安装指南:https://docs.ansible.com/"
|
||||
;;
|
||||
*)
|
||||
echo "👉 其他平台请参考:https://docs.ansible.com/"
|
||||
;;
|
||||
esac
|
||||
exit 1
|
||||
else
|
||||
echo "✅ Ansible 已安装: $(ansible --version | head -n 1)"
|
||||
fi
|
||||
}
|
||||
|
||||
# ========== 检查 Terraform ==========
|
||||
ensure_terraform() {
|
||||
if ! command -v terraform &> /dev/null; then
|
||||
echo "❌ 未检测到 Terraform,请手动安装:"
|
||||
echo "👉 https://developer.hashicorp.com/terraform/install"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Terraform 已安装: $(terraform version | head -n1)"
|
||||
}
|
||||
|
||||
# ========== 环境初始化检查 ==========
|
||||
init_env() {
|
||||
echo "⚙️ 初始化 Pulumi + Ansible 环境..."
|
||||
|
||||
# 1️⃣ 检查 Pulumi
|
||||
ensure_pulumi
|
||||
|
||||
# 2️⃣ 安装 Python 依赖
|
||||
if [ -f "$PULUMI_DIR/requirements.txt" ]; then
|
||||
echo "📦 安装 Python 依赖..."
|
||||
pip3 install -r "$PULUMI_DIR/requirements.txt"
|
||||
fi
|
||||
|
||||
# 3️⃣ 检查 Ansible
|
||||
ensure_ansible
|
||||
|
||||
# 4️⃣ 检查 Terraform(可选)
|
||||
if [ -d "$TERRAFORM_DIR" ]; then
|
||||
ensure_terraform
|
||||
fi
|
||||
|
||||
# 5️⃣ 初始化 Pulumi Stack
|
||||
cd "$PULUMI_DIR"
|
||||
pulumi login --local > /dev/null
|
||||
if ! pulumi stack ls | grep -q "$STACK_NAME"; then
|
||||
echo "📂 创建 Pulumi Stack: $STACK_NAME"
|
||||
pulumi stack init "$STACK_NAME"
|
||||
else
|
||||
echo "✅ Stack 已存在:$STACK_NAME"
|
||||
fi
|
||||
|
||||
echo "✅ 初始化完成 ✅"
|
||||
}
|
||||
|
||||
# ========== 执行 Pulumi ==========
|
||||
pulumi_run() {
|
||||
cd "$PULUMI_DIR"
|
||||
case "$ACTION" in
|
||||
up)
|
||||
if [ ! -d "$CONFIG_PATH" ] || [ -z "$(ls -A $CONFIG_PATH/*.yaml 2>/dev/null)" ]; then
|
||||
echo "⚠️ 配置目录为空:$CONFIG_PATH,跳过部署"
|
||||
exit 0
|
||||
fi
|
||||
echo "🚀 正在部署 stack: $STACK_NAME"
|
||||
pulumi up --yes
|
||||
;;
|
||||
down)
|
||||
echo "🔥 正在销毁 stack: $STACK_NAME"
|
||||
pulumi destroy --yes
|
||||
;;
|
||||
delete)
|
||||
echo "🗑️ 删除 Stack: $STACK_NAME"
|
||||
pulumi stack rm "$STACK_NAME" --yes
|
||||
;;
|
||||
export)
|
||||
echo "📤 导出 stack 状态"
|
||||
pulumi stack export --file stack-export.json
|
||||
;;
|
||||
import)
|
||||
echo "📥 导入 stack 状态"
|
||||
pulumi stack import --file stack-export.json
|
||||
;;
|
||||
init)
|
||||
init_env
|
||||
;;
|
||||
*)
|
||||
print_help
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ========== 执行 Ansible ==========
|
||||
run_ansible() {
|
||||
if [ ! -f scripts/inventory.py ]; then
|
||||
echo "❌ 未找到 scripts/inventory.py"
|
||||
exit 1
|
||||
fi
|
||||
echo "🧪 执行 Ansible Playbook"
|
||||
ansible-playbook -i scripts/inventory.py "$ANSIBLE_DIR/playbooks/setup.yml"
|
||||
}
|
||||
|
||||
# ========== 分发 ==========
|
||||
case "$ACTION" in
|
||||
up|down|delete|export|import|init)
|
||||
export CONFIG_PATH
|
||||
export STACK_ENV
|
||||
pulumi_run
|
||||
;;
|
||||
ansible)
|
||||
run_ansible
|
||||
;;
|
||||
help|*)
|
||||
print_help
|
||||
;;
|
||||
esac
|
||||
Loading…
Reference in New Issue
Block a user