🪪 本文作者:许业宝
✍️ 作者信息:
🌞 VSTECS 云解决方案架构师 | AWS APN Ambassador |
🪪 AWS Community Builder | 亚马逊云科技技能云博主 | UGL
⭐ 已获得 13 项 AWS 认证 | CKA、CKS认证 |
🏆 CSDN博客专家 | CSDN云计算领域优质创作者
🚀 1. 前言
一个完整的 AWS Organizations 账号管理自动化解决方案,支持定时触发账号邀请、解绑操作,并提供邮件通知功能。
📢 2. 实验背景
2.1 业务场景
在企业级 AWS 环境中,经常需要:
- 批量管理成员账号:定期邀请新账号加入组织
- 自动化解绑流程:处理欠费或不再使用的账号
- 合规性要求:确保账号管理操作的可追溯性
- 成本优化:及时移除不必要的账号以降低管理成本
2.2 传统方式的痛点
- 手动操作繁琐:通过控制台逐个处理账号
- 容易出错:人工操作可能遗漏重要检查
- 缺乏自动化:无法按计划自动执行
- 通知不及时:操作完成后缺乏及时反馈
2.3 解决方案优势
- ✅ 全自动化:支持定时触发,无需人工干预
- ✅ 安全可靠:内置预检查机制,防止误操作
- ✅ 实时通知:操作完成后自动发送邮件报告
- ✅ 灵活配置:支持多种操作模式和时间安排
- ✅ 完整日志:所有操作都有详细的审计日志
2.4 核心功能
1 账号邀请管理
- 🔄 定时邀请:按计划自动邀请指定账号加入组织
- 📧 邀请跟踪:记录邀请状态和握手ID
- ⚡ 批量处理:支持同时处理多个账号
- 账号解绑管理
- 🔍 预检查机制:解绑前自动检查账号状态和依赖关系
- 🛡️ 安全保护:防止误删管理账号或关键账号
- 📋 详细报告:提供解绑操作的完整报告
- 账号状态检查
- 👀 实时监控:定期检查账号在组织中的状态
- 📊 状态报告:生成详细的账号状态报告
- 🔔 异常告警:发现异常状态时及时通知
- 邮件通知系统
- 📧 自动通知:操作完成后自动发送邮件
- 📈 详细报告:包含操作结果、统计信息和建议
- 🕐 时间戳:所有通知都包含准确的时间信息
📁 3. 项目结构
bash
Lambda-Organization-Unbind/
├── 🔧 核心文件
│ ├── lambda_function.py # Lambda 函数主体
│ └── requirements.txt # Python 依赖
├── 🚀 部署工具
│ ├── deploy.sh # 自动化部署脚本
│ ├── test.sh # 功能测试脚本
│ ├── view_logs.sh # 日志查看工具
│ └── destroy.sh # 资源清理脚本
├── 📋 配置文件
│ └── terraform/
│ ├── main.tf # 基础设施配置
│ └── terraform.tfvars # 参数配置
├── 🗑️ 已废弃文件
│ ├── enhanced_lambda_function.py # 旧版本函数
│ ├── account_checker.py # 旧版本检查器
│ └── view_results.sh # 旧版本脚本
└── 📖 文档
└── README.md # 使用说明
🔬 4. 实验架构图

🛠️ 快速开始
前置要求
- AWS 环境
- AWS CLI 已安装并配置
- 当前账号为 AWS Organizations 管理账号
- 具备完整的 Organizations 权限
- 本地工具
- Terraform >= 1.0
- Python 3.9+
- Bash shell
- 权限要求
bash
{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Action": ["organizations:*","lambda:*","iam:*","sns:*","events:*","logs:*"],"Resource": "*"}]}
🖇️ 5. 安装步骤
1️⃣ 克隆项目
bash
git clone <repository-url>
cd Lambda-Organization-Unbind
2️⃣ 配置参数
编辑 terraform/terraform.tfvars:
bash
# AWS 配置
aws_region = "us-east-1"
lambda_function_name = "org-account-manager"
# 邀请操作配置
enable_invite_schedule = true
invite_schedule = "cron(0 9 * * MON *)" # 每周一上午9点
invite_account_ids = [
"123456789012", # 替换为实际账号ID
"234567890123"
]
# 解绑操作配置
enable_unbind_schedule = true
unbind_schedule = "cron(0 2 * * SUN *)" # 每周日凌晨2点
unbind_account_ids = [
"345678901234" # 替换为实际账号ID
]
# 检查操作配置
enable_check_schedule = true
check_schedule = "cron(0 8 * * ? *)" # 每天上午8点
check_account_ids = [
"456789012345" # 替换为实际账号ID
]
# 通知邮箱
notification_email = "admin@example.com"
3️⃣ 部署系统
bash
# 自动化部署
./deploy.sh
# 或手动部署cd terraform
terraform init
terraform plan
terraform apply
4️⃣ 验证部署
bash
# 测试检查功能
./test.sh check 123456789012
# 查看日志
./view_logs.sh recent
📋 6. 配置说明
定时任务配置

Cron 表达式格式
bash
cron(分钟 小时 日期 月份 星期 年份)
常用示例:
- cron(0 8 * * ? *) - 每天上午8点
- cron(0 9 * * MON *) - 每周一上午9点
- cron(0 2 1 * ? *) - 每月1日凌晨2点
- cron(0 0 * * SUN *) - 每周日午夜
🔧 7. 使用指南
修改配置
- 编辑配置文件
bash
vim terraform/terraform.tfvars
- 重新部署
bash
./deploy.sh
📧 邮件通知
通知内容
邮件通知包含以下信息:
- 操作摘要:操作类型、执行时间、处理结果统计
- 详细结果:每个账号的处理状态和消息
- 操作建议:后续步骤和注意事项
- 故障排除:如有错误,提供解决建议
通知示例
bash
主题:AWS Organizations 账号解绑 - 1个成功
AWS Organizations 从组织移除操作报告
============================================
执行时间: 2024-12-26 14:30:00 (北京时间)
操作类型: UNBIND
总计处理: 1个账号
成功: 1个
失败: 0个
详细结果:
----------------------------------------
✅ 账号 123456789012: 账号已成功从组织中移除
后续步骤:
- 可以重新运行检查确认状态
⚠️ 重要注意事项
安全要求
- ✅ 必须在管理账号中执行:只有组织管理账号才能执行这些操作
- ✅ 权限最小化原则:仅授予必要的权限
- ✅ 定期审计:定期检查和审计操作日志
操作原则
- 测试优先:在生产环境使用前,先在测试环境验证
- 分批执行:大量账号建议分批处理
- 业务低峰:选择业务低峰期执行解绑操作
- 备份重要数据:操作前确保重要数据已备份
风险提醒
- 🚨 解绑操作不可逆:账号一旦从组织中移除,需要重新邀请才能加入
- 🚨 计费信息要求:通过组织创建的账号需要完善计费信息才能独立
- 🚨 服务依赖:解绑前请确认没有跨账号的服务依赖
🧑🏻💻 8. 核心代码
terraform.tfvars
bash
# AWS 配置
aws_region = "us-east-1"
# Lambda 函数配置
lambda_function_name = "org-account-manager"
# 邀请操作配置
enable_invite_schedule = true
invite_schedule = "cron(*/3 * * * ? *)" # 固定频率五分钟
invite_account_ids = [
# "123456789012", # 示例账号ID
]
# 解绑操作配置
# enable_unbind_schedule = true
# unbind_schedule = "cron(*/5 * * * ? *)" # 每周日凌晨2点
# unbind_account_ids = [
# "123456789012" # 示例账号ID
# ]
# # 检查操作配置
# enable_check_schedule = true
# check_schedule = "cron(*/5 * * * ? *)" # 固定频率五分钟
# check_account_ids = [
# "123456789012" # 示例账号ID
# ]
# 通知邮箱
notification_email = "xxxxx@outlook.com"
lambda_function.py
bash
import json
import boto3
import logging
import os
from datetime import datetime, timezone, timedelta
from botocore.exceptions import ClientError
from typing import Dict, List, Any
# 配置日志
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
"""
AWS Organizations 账号管理Lambda函数
支持:
1. 定时触发邀请成员账号加入组织
2. 定时触发从组织中移除成员账号
3. 发送邮件通知
"""
# 获取操作参数
operation_type = event.get('operation_type', 'check') # invite, unbind, check
target_account_ids = event.get('account_ids', [])
if not target_account_ids:
logger.error("未提供目标账号ID")
return create_response(400, "未提供目标账号ID")
logger.info(f"开始执行操作: {operation_type}, 目标账号: {target_account_ids}")
try:
if operation_type == 'invite':
results = handle_invite_accounts(target_account_ids)
elif operation_type == 'unbind':
results = handle_unbind_accounts(target_account_ids)
elif operation_type == 'check':
results = handle_check_accounts(target_account_ids)
else:
return create_response(400, f"不支持的操作类型: {operation_type}")
# 发送通知邮件
send_operation_notification(operation_type, results)
return create_response(200, f"{operation_type}操作完成", results)
except Exception as e:
logger.error(f"执行操作时发生错误: {str(e)}")
return create_response(500, f"操作失败: {str(e)}")
def handle_invite_accounts(account_ids: List[str]) -> List[Dict[str, Any]]:
"""处理邀请账号加入组织"""
org_client = boto3.client('organizations')
results = []
for account_id in account_ids:
try:
logger.info(f"邀请账号 {account_id} 加入组织")
# 检查账号是否已在组织中
try:
org_client.describe_account(AccountId=account_id)
results.append({
'account_id': account_id,
'status': 'already_member',
'message': '账号已是组织成员'
})
continue
except ClientError as e:
if e.response['Error']['Code'] != 'AccountNotFoundException':
raise e
# 发送邀请
response = org_client.invite_account_to_organization(
Target={
'Id': account_id,
'Type': 'ACCOUNT'
},
Notes=f'自动邀请账号 {account_id} 加入组织'
)
results.append({
'account_id': account_id,
'status': 'invited',
'message': '邀请已发送',
'handshake_id': response['Handshake']['Id']
})
logger.info(f"成功邀请账号 {account_id}, 握手ID: {response['Handshake']['Id']}")
except ClientError as e:
error_code = e.response['Error']['Code']
error_message = e.response['Error']['Message']
results.append({
'account_id': account_id,
'status': 'error',
'message': f'{error_code}: {error_message}'
})
logger.error(f"邀请账号 {account_id} 失败: {error_code} - {error_message}")
return results
def handle_unbind_accounts(account_ids: List[str]) -> List[Dict[str, Any]]:
"""处理从组织中移除账号"""
org_client = boto3.client('organizations')
results = []
for account_id in account_ids:
try:
logger.info(f"开始移除账号 {account_id}")
# 预检查
check_result = perform_unbind_check(account_id)
if not check_result['can_unbind']:
results.append({
'account_id': account_id,
'status': 'blocked',
'message': check_result['message'],
'issues': check_result.get('issues', [])
})
continue
# 执行移除
org_client.remove_account_from_organization(AccountId=account_id)
results.append({
'account_id': account_id,
'status': 'success',
'message': '账号已成功从组织中移除'
})
logger.info(f"成功移除账号 {account_id}")
except ClientError as e:
error_code = e.response['Error']['Code']
error_message = e.response['Error']['Message']
if error_code == 'ConstraintViolationException':
results.append({
'account_id': account_id,
'status': 'manual_required',
'message': '账号缺少独立所需信息,需要手动处理',
'details': error_message
})
else:
results.append({
'account_id': account_id,
'status': 'error',
'message': f'{error_code}: {error_message}'
})
logger.error(f"移除账号 {account_id} 失败: {error_code} - {error_message}")
return results
def handle_check_accounts(account_ids: List[str]) -> List[Dict[str, Any]]:
"""检查账号状态"""
results = []
for account_id in account_ids:
try:
check_result = perform_comprehensive_check(account_id)
results.append(check_result)
except Exception as e:
results.append({
'account_id': account_id,
'status': 'error',
'message': str(e)
})
return results
def perform_unbind_check(account_id: str) -> Dict[str, Any]:
"""执行解绑前检查"""
org_client = boto3.client('organizations')
try:
# 检查账号是否存在
account_info = org_client.describe_account(AccountId=account_id)
account = account_info['Account']
# 检查是否为管理账号
org_info = org_client.describe_organization()
if account_id == org_info['Organization']['MasterAccountId']:
return {
'can_unbind': False,
'message': '管理账号不能从组织中移除'
}
# 检查账号状态
if account['Status'] != 'ACTIVE':
return {
'can_unbind': False,
'message': f'账号状态不是ACTIVE: {account["Status"]}'
}
return {
'can_unbind': True,
'message': '账号可以安全移除',
'account_name': account['Name'],
'joined_method': account['JoinedMethod']
}
except ClientError as e:
if e.response['Error']['Code'] == 'AccountNotFoundException':
return {
'can_unbind': False,
'message': '账号不存在于当前组织中'
}
else:
return {
'can_unbind': False,
'message': f'检查失败: {e.response["Error"]["Message"]}'
}
def perform_comprehensive_check(account_id: str) -> Dict[str, Any]:
"""执行全面检查"""
org_client = boto3.client('organizations')
try:
# 检查账号是否在组织中
try:
account_info = org_client.describe_account(AccountId=account_id)
account = account_info['Account']
return {
'account_id': account_id,
'status': 'in_organization',
'account_name': account['Name'],
'account_email': account['Email'],
'account_status': account['Status'],
'joined_method': account['JoinedMethod'],
'joined_date': account['JoinedTimestamp'].isoformat(),
'can_invite': False,
'can_unbind': account['Status'] == 'ACTIVE'
}
except ClientError as e:
if e.response['Error']['Code'] == 'AccountNotFoundException':
return {
'account_id': account_id,
'status': 'not_in_organization',
'message': '账号不在组织中',
'can_invite': True,
'can_unbind': False
}
else:
raise e
except Exception as e:
return {
'account_id': account_id,
'status': 'error',
'message': str(e),
'can_invite': False,
'can_unbind': False
}
def send_operation_notification(operation_type: str, results: List[Dict[str, Any]]):
"""发送操作通知邮件"""
try:
sns_topic_arn = os.environ.get('SNS_TOPIC_ARN')
if not sns_topic_arn:
logger.warning("未配置SNS_TOPIC_ARN,跳过邮件通知")
return
# 统计结果
total_count = len(results)
success_count = len([r for r in results if r['status'] in ['success', 'invited']])
error_count = len([r for r in results if r['status'] == 'error'])
# 构建邮件主题
operation_names = {
'invite': '邀请加入组织',
'unbind': '从组织移除',
'check': '账号状态检查'
}
subject = f"AWS Organizations {operation_names.get(operation_type, operation_type)} - "
if error_count > 0:
subject += f"{error_count}个失败"
else:
subject += f"{success_count}个成功"
# 构建邮件内容
beijing_time = datetime.now(timezone.utc) + timedelta(hours=8)
message_lines = [
f"AWS Organizations {operation_names.get(operation_type, operation_type)}操作报告",
"=" * 60,
f"执行时间: {beijing_time.strftime('%Y-%m-%d %H:%M:%S')} (北京时间)",
f"操作类型: {operation_type.upper()}",
"",
f"总计处理: {total_count}个账号",
f"成功: {success_count}个",
f"失败: {error_count}个",
"",
"详细结果:",
"-" * 40
]
# 添加每个账号的处理结果
for result in results:
account_id = result['account_id']
status = result['status']
message = result.get('message', '')
status_emoji = {
'success': '✅',
'invited': '📧',
'already_member': '👥',
'blocked': '🚫',
'manual_required': '⚠️',
'error': '❌',
'in_organization': '👥',
'not_in_organization': '🔍'
}.get(status, '❓')
message_lines.append(f"{status_emoji} 账号 {account_id}: {message}")
# 添加额外信息
if 'account_name' in result:
message_lines.append(f" 账号名称: {result['account_name']}")
if 'handshake_id' in result:
message_lines.append(f" 握手ID: {result['handshake_id']}")
if 'issues' in result and result['issues']:
message_lines.append(f" 问题: {', '.join(result['issues'])}")
# 添加后续步骤建议
if operation_type == 'invite':
message_lines.extend([
"",
"后续步骤:",
"- 已发送邀请的账号需要接受邀请才能加入组织",
"- 可以在AWS Organizations控制台查看邀请状态"
])
elif operation_type == 'unbind':
manual_required = [r for r in results if r['status'] == 'manual_required']
if manual_required:
message_lines.extend([
"",
"需要手动处理的账号:",
"- 使用根用户登录目标账号",
"- 完善计费信息(付款方式、联系信息、税务设置)",
"- 在Organizations控制台选择'离开组织'"
])
message = "\n".join(message_lines)
# 发送通知
sns_client = boto3.client('sns')
response = sns_client.publish(
TopicArn=sns_topic_arn,
Subject=subject,
Message=message
)
logger.info(f"通知邮件已发送,MessageId: {response['MessageId']}")
except Exception as e:
logger.error(f"发送通知邮件失败: {str(e)}")
def create_response(status_code: int, message: str, data: Any = None) -> Dict[str, Any]:
"""创建标准响应"""
response = {
'statusCode': status_code,
'body': json.dumps({
'message': message,
'timestamp': datetime.utcnow().isoformat(),
'data': data
}, default=str, ensure_ascii=False)
}
return response
bash
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# 变量定义
variable "aws_region" {
description = "AWS 区域"
type = string
default = "us-east-1"
}
variable "lambda_function_name" {
description = "Lambda 函数名称"
type = string
default = "org-account-manager"
}
# 邀请操作配置
variable "invite_schedule" {
description = "邀请操作的调度表达式"
type = string
default = "cron(0 9 * * MON *)" # 每周一上午9点
}
variable "invite_account_ids" {
description = "要邀请加入组织的账号ID列表"
type = list(string)
default = []
}
# 解绑操作配置
variable "unbind_schedule" {
description = "解绑操作的调度表达式"
type = string
default = "cron(0 2 * * SUN *)" # 每周日凌晨2点
}
variable "unbind_account_ids" {
description = "要从组织中移除的账号ID列表"
type = list(string)
default = []
}
# 检查操作配置
variable "check_schedule" {
description = "检查操作的调度表达式"
type = string
default = "cron(0 8 * * ? *)" # 每天上午8点
}
variable "check_account_ids" {
description = "要检查状态的账号ID列表"
type = list(string)
default = []
}
variable "enable_invite_schedule" {
description = "启用邀请定时任务"
type = bool
default = false
}
variable "enable_unbind_schedule" {
description = "启用解绑定时任务"
type = bool
default = false
}
variable "enable_check_schedule" {
description = "启用检查定时任务"
type = bool
default = true
}
variable "notification_email" {
description = "通知邮箱地址"
type = string
default = ""
}
# IAM 角色 - Lambda 执行角色
resource "aws_iam_role" "lambda_execution_role" {
name = "${var.lambda_function_name}-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
# IAM 策略 - Organizations 权限
resource "aws_iam_policy" "lambda_organizations_policy" {
name = "${var.lambda_function_name}-organizations-policy"
description = "允许 Lambda 函数操作 AWS Organizations"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"organizations:DescribeOrganization",
"organizations:DescribeAccount",
"organizations:ListAccounts",
"organizations:ListParents",
"organizations:ListPoliciesForTarget",
"organizations:RemoveAccountFromOrganization",
"organizations:InviteAccountToOrganization",
"organizations:ListHandshakesForOrganization",
"organizations:DescribeHandshake"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"iam:ListRoles",
"ram:GetResourceShares",
"ram:GetResourceShareAssociations"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"sns:Publish"
]
Resource = aws_sns_topic.notifications.arn
},
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:*:*:*"
}
]
})
}
# 附加策略到角色
resource "aws_iam_role_policy_attachment" "lambda_organizations_policy_attachment" {
role = aws_iam_role.lambda_execution_role.name
policy_arn = aws_iam_policy.lambda_organizations_policy.arn
}
# 附加基本执行策略
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
role = aws_iam_role.lambda_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# 创建部署包
data "archive_file" "lambda_zip" {
type = "zip"
source_dir = "../"
output_path = "lambda_function.zip"
excludes = ["terraform", "*.md", "*.sh", "*.tfstate*", ".terraform*", "enhanced_lambda_function.py", "account_checker.py"]
}
# SNS 主题和订阅
resource "aws_sns_topic" "notifications" {
name = "org-account-manager-notifications"
tags = {
Name = "org-account-manager-notifications"
Environment = "production"
Purpose = "org-account-management"
}
}
# SNS 订阅(如果配置了邮箱)
resource "aws_sns_topic_subscription" "email_notification" {
count = var.notification_email != "" ? 1 : 0
topic_arn = aws_sns_topic.notifications.arn
protocol = "email"
endpoint = var.notification_email
}
# Lambda 函数
resource "aws_lambda_function" "org_manager_function" {
filename = data.archive_file.lambda_zip.output_path
function_name = var.lambda_function_name
role = aws_iam_role.lambda_execution_role.arn
handler = "lambda_function.lambda_handler"
runtime = "python3.9"
timeout = 900
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
environment {
variables = {
LOG_LEVEL = "INFO"
SNS_TOPIC_ARN = aws_sns_topic.notifications.arn
}
}
tags = {
Name = var.lambda_function_name
Environment = "production"
Purpose = "org-account-management"
}
}
# CloudWatch 日志组
resource "aws_cloudwatch_log_group" "lambda_logs" {
name = "/aws/lambda/${var.lambda_function_name}"
retention_in_days = 14
}
# EventBridge 规则 - 邀请操作
resource "aws_cloudwatch_event_rule" "invite_schedule" {
count = var.enable_invite_schedule ? 1 : 0
name = "${var.lambda_function_name}-invite-schedule"
description = "定时触发账号邀请加入组织"
schedule_expression = var.invite_schedule
tags = {
Name = "${var.lambda_function_name}-invite-schedule"
}
}
resource "aws_cloudwatch_event_target" "invite_target" {
count = var.enable_invite_schedule ? 1 : 0
rule = aws_cloudwatch_event_rule.invite_schedule[0].name
target_id = "TriggerInviteFunction"
arn = aws_lambda_function.org_manager_function.arn
input = jsonencode({
operation_type = "invite"
account_ids = var.invite_account_ids
})
}
resource "aws_lambda_permission" "allow_invite_eventbridge" {
count = var.enable_invite_schedule ? 1 : 0
statement_id = "AllowInviteFromEventBridge"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.org_manager_function.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.invite_schedule[0].arn
}
# EventBridge 规则 - 解绑操作
resource "aws_cloudwatch_event_rule" "unbind_schedule" {
count = var.enable_unbind_schedule ? 1 : 0
name = "${var.lambda_function_name}-unbind-schedule"
description = "定时触发账号从组织中移除"
schedule_expression = var.unbind_schedule
tags = {
Name = "${var.lambda_function_name}-unbind-schedule"
}
}
resource "aws_cloudwatch_event_target" "unbind_target" {
count = var.enable_unbind_schedule ? 1 : 0
rule = aws_cloudwatch_event_rule.unbind_schedule[0].name
target_id = "TriggerUnbindFunction"
arn = aws_lambda_function.org_manager_function.arn
input = jsonencode({
operation_type = "unbind"
account_ids = var.unbind_account_ids
})
}
resource "aws_lambda_permission" "allow_unbind_eventbridge" {
count = var.enable_unbind_schedule ? 1 : 0
statement_id = "AllowUnbindFromEventBridge"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.org_manager_function.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.unbind_schedule[0].arn
}
# EventBridge 规则 - 检查操作
resource "aws_cloudwatch_event_rule" "check_schedule" {
count = var.enable_check_schedule ? 1 : 0
name = "${var.lambda_function_name}-check-schedule"
description = "定时检查账号状态"
schedule_expression = var.check_schedule
tags = {
Name = "${var.lambda_function_name}-check-schedule"
}
}
resource "aws_cloudwatch_event_target" "check_target" {
count = var.enable_check_schedule ? 1 : 0
rule = aws_cloudwatch_event_rule.check_schedule[0].name
target_id = "TriggerCheckFunction"
arn = aws_lambda_function.org_manager_function.arn
input = jsonencode({
operation_type = "check"
account_ids = var.check_account_ids
})
}
resource "aws_lambda_permission" "allow_check_eventbridge" {
count = var.enable_check_schedule ? 1 : 0
statement_id = "AllowCheckFromEventBridge"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.org_manager_function.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.check_schedule[0].arn
}
# 输出
output "lambda_function_arn" {
description = "Lambda 函数 ARN"
value = aws_lambda_function.org_manager_function.arn
}
output "lambda_function_name" {
description = "Lambda 函数名称"
value = aws_lambda_function.org_manager_function.function_name
}
output "sns_topic_arn" {
description = "SNS 通知主题 ARN"
value = aws_sns_topic.notifications.arn
}
output "scheduled_operations" {
description = "已配置的定时操作"
value = {
invite_enabled = var.enable_invite_schedule
invite_schedule = var.enable_invite_schedule ? var.invite_schedule : null
invite_accounts = var.enable_invite_schedule ? length(var.invite_account_ids) : 0
unbind_enabled = var.enable_unbind_schedule
unbind_schedule = var.enable_unbind_schedule ? var.unbind_schedule : null
unbind_accounts = var.enable_unbind_schedule ? length(var.unbind_account_ids) : 0
check_enabled = var.enable_check_schedule
check_schedule = var.enable_check_schedule ? var.check_schedule : null
check_accounts = var.enable_check_schedule ? length(var.check_account_ids) : 0
}
}
output "notification_setup" {
description = "通知设置信息"
value = var.notification_email != "" ? {
email_configured = true
email_address = var.notification_email
note = "请检查邮箱并确认 SNS 订阅"
} : {
email_configured = false
note = "未配置邮箱通知"
}
}