亚马逊云 Organizations 组织 Link 账号关联与解绑自动化解决方案

🪪 本文作者:许业宝

✍️ 作者信息:

🌞 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
  • ⚡ 批量处理:支持同时处理多个账号
  1. 账号解绑管理
  • 🔍 预检查机制:解绑前自动检查账号状态和依赖关系
  • 🛡️ 安全保护:防止误删管理账号或关键账号
  • 📋 详细报告:提供解绑操作的完整报告
  1. 账号状态检查
  • 👀 实时监控:定期检查账号在组织中的状态
  • 📊 状态报告:生成详细的账号状态报告
  • 🔔 异常告警:发现异常状态时及时通知
  1. 邮件通知系统
  • 📧 自动通知:操作完成后自动发送邮件
  • 📈 详细报告:包含操作结果、统计信息和建议
  • 🕐 时间戳:所有通知都包含准确的时间信息

📁 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. 实验架构图

🛠️ 快速开始

前置要求

  1. AWS 环境
  • AWS CLI 已安装并配置
  • 当前账号为 AWS Organizations 管理账号
  • 具备完整的 Organizations 权限
  1. 本地工具
  • Terraform >= 1.0
  • Python 3.9+
  • Bash shell
  1. 权限要求
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. 使用指南

修改配置

  1. 编辑配置文件
bash 复制代码
vim terraform/terraform.tfvars
  1. 重新部署
bash 复制代码
./deploy.sh

📧 邮件通知

通知内容

邮件通知包含以下信息:

  • 操作摘要:操作类型、执行时间、处理结果统计
  • 详细结果:每个账号的处理状态和消息
  • 操作建议:后续步骤和注意事项
  • 故障排除:如有错误,提供解决建议

通知示例

bash 复制代码
主题:AWS Organizations 账号解绑 - 1个成功

AWS Organizations 从组织移除操作报告
============================================
执行时间: 2024-12-26 14:30:00 (北京时间)
操作类型: UNBIND

总计处理: 1个账号
成功: 1个
失败: 0个

详细结果:
----------------------------------------
✅ 账号 123456789012: 账号已成功从组织中移除

后续步骤:
- 可以重新运行检查确认状态

⚠️ 重要注意事项

安全要求

  • ✅ 必须在管理账号中执行:只有组织管理账号才能执行这些操作
  • ✅ 权限最小化原则:仅授予必要的权限
  • ✅ 定期审计:定期检查和审计操作日志

操作原则

  1. 测试优先:在生产环境使用前,先在测试环境验证
  2. 分批执行:大量账号建议分批处理
  3. 业务低峰:选择业务低峰期执行解绑操作
  4. 备份重要数据:操作前确保重要数据已备份

风险提醒

  • 🚨 解绑操作不可逆:账号一旦从组织中移除,需要重新邀请才能加入
  • 🚨 计费信息要求:通过组织创建的账号需要完善计费信息才能独立
  • 🚨 服务依赖:解绑前请确认没有跨账号的服务依赖

🧑🏻‍💻 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

main.tf

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 = "未配置邮箱通知"
  }
}
相关推荐
慕容雪_15 小时前
运维笔记-网络【属性】-【共享】中没有【家庭网络连接(H)】的选项
运维·网络·共享
倪某某15 小时前
阿里云无影GPU部署WAN2.2模型
阿里云·云计算
北京耐用通信15 小时前
耐达讯自动化赋能:Canopen转Profibus网关水泵连接新范式
人工智能·科技·物联网·自动化·信息与通信
AC赳赳老秦15 小时前
Shell 脚本批量生成:DeepSeek 辅助编写服务器运维自动化指令
运维·服务器·前端·vue.js·数据分析·自动化·deepseek
倪某某16 小时前
阿里云ECS GPU部署WAN2.2
人工智能·阿里云·云计算
学Linux的语莫16 小时前
linux的root目录缓存清理
linux·运维·服务器
oMcLin16 小时前
如何在 SUSE Linux Enterprise Server 15 上部署并优化 K3s 集群,提升轻量级容器化应用的资源利用率?
linux·运维·服务器
Run Out Of Brain16 小时前
解决nginx代理配置下wordpress的 /wp-admin/返回 302 重定向到登录页问题
运维·nginx
Ghost Face...16 小时前
深入解析YT6801驱动模块架构
linux·运维·服务器