""" Serverless stack generator for AWS. Creates CloudFormation/CDK templates for serverless applications. """ from typing import Dict, List, Any, Optional class ServerlessStackGenerator: """Generate serverless application stacks.""" def __init__(self, app_name: str, requirements: Dict[str, Any]): """ Initialize with application requirements. Args: app_name: Application name (used for resource naming) requirements: Dictionary with API, database, auth requirements """ self.app_name = app_name.lower().replace(' ', '-') self.requirements = requirements self.region = requirements.get('region', 'us-east-1') def generate_cloudformation_template(self) -> str: """ Generate CloudFormation template for serverless stack. Returns: YAML CloudFormation template as string """ template = f"""AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Serverless stack for {self.app_name} Parameters: Environment: Type: String Default: dev AllowedValues: - dev - staging - production Description: Deployment environment CorsAllowedOrigins: Type: String Default: '*' Description: CORS allowed origins for API Gateway Resources: # DynamoDB Table {self.app_name.replace('-', '')}Table: Type: AWS::DynamoDB::Table Properties: TableName: !Sub '${{Environment}}-{self.app_name}-data' BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: PK AttributeType: S - AttributeName: SK AttributeType: S KeySchema: - AttributeName: PK KeyType: HASH - AttributeName: SK KeyType: RANGE PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true SSESpecification: SSEEnabled: true StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES Tags: - Key: Environment Value: !Ref Environment - Key: Application Value: {self.app_name} # Lambda Execution Role LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DynamoDBAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem - dynamodb:Query - dynamodb:Scan Resource: !GetAtt {self.app_name.replace('-', '')}Table.Arn # Lambda Function ApiFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Sub '${{Environment}}-{self.app_name}-api' Handler: index.handler Runtime: nodejs18.x CodeUri: ./src MemorySize: 512 Timeout: 10 Role: !GetAtt LambdaExecutionRole.Arn Environment: Variables: TABLE_NAME: !Ref {self.app_name.replace('-', '')}Table ENVIRONMENT: !Ref Environment Events: ApiEvent: Type: Api Properties: Path: /{{proxy+}} Method: ANY RestApiId: !Ref ApiGateway Tags: Environment: !Ref Environment Application: {self.app_name} # API Gateway ApiGateway: Type: AWS::Serverless::Api Properties: Name: !Sub '${{Environment}}-{self.app_name}-api' StageName: !Ref Environment Cors: AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'" AllowHeaders: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'" AllowOrigin: !Sub "'${{CorsAllowedOrigins}}'" Auth: DefaultAuthorizer: CognitoAuthorizer Authorizers: CognitoAuthorizer: UserPoolArn: !GetAtt UserPool.Arn ThrottleSettings: BurstLimit: 200 RateLimit: 100 Tags: Environment: !Ref Environment Application: {self.app_name} # Cognito User Pool UserPool: Type: AWS::Cognito::UserPool Properties: UserPoolName: !Sub '${{Environment}}-{self.app_name}-users' UsernameAttributes: - email AutoVerifiedAttributes: - email Policies: PasswordPolicy: MinimumLength: 8 RequireUppercase: true RequireLowercase: true RequireNumbers: true RequireSymbols: false MfaConfiguration: OPTIONAL EnabledMfas: - SOFTWARE_TOKEN_MFA UserAttributeUpdateSettings: AttributesRequireVerificationBeforeUpdate: - email Schema: - Name: email Required: true Mutable: true # Cognito User Pool Client UserPoolClient: Type: AWS::Cognito::UserPoolClient Properties: ClientName: !Sub '${{Environment}}-{self.app_name}-client' UserPoolId: !Ref UserPool GenerateSecret: false RefreshTokenValidity: 30 AccessTokenValidity: 1 IdTokenValidity: 1 TokenValidityUnits: RefreshToken: days AccessToken: hours IdToken: hours ExplicitAuthFlows: - ALLOW_USER_SRP_AUTH - ALLOW_REFRESH_TOKEN_AUTH # CloudWatch Log Group ApiLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub '/aws/lambda/${{Environment}}-{self.app_name}-api' RetentionInDays: 7 Outputs: ApiUrl: Description: API Gateway endpoint URL Value: !Sub 'https://${{ApiGateway}}.execute-api.${{AWS::Region}}.amazonaws.com/${{Environment}}' Export: Name: !Sub '${{Environment}}-{self.app_name}-ApiUrl' UserPoolId: Description: Cognito User Pool ID Value: !Ref UserPool Export: Name: !Sub '${{Environment}}-{self.app_name}-UserPoolId' UserPoolClientId: Description: Cognito User Pool Client ID Value: !Ref UserPoolClient Export: Name: !Sub '${{Environment}}-{self.app_name}-UserPoolClientId' TableName: Description: DynamoDB Table Name Value: !Ref {self.app_name.replace('-', '')}Table Export: Name: !Sub '${{Environment}}-{self.app_name}-TableName' """ return template def generate_cdk_stack(self) -> str: """ Generate AWS CDK stack in TypeScript. Returns: CDK stack code as string """ stack = f"""import * as cdk from 'aws-cdk-lib'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as apigateway from 'aws-cdk-lib/aws-apigateway'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as cognito from 'aws-cdk-lib/aws-cognito'; import {{ Construct }} from 'constructs'; export class {self.app_name.replace('-', '').title()}Stack extends cdk.Stack {{ constructor(scope: Construct, id: string, props?: cdk.StackProps) {{ super(scope, id, props); // DynamoDB Table const table = new dynamodb.Table(this, '{self.app_name}Table', {{ tableName: `${{cdk.Stack.of(this).stackName}}-data`, partitionKey: {{ name: 'PK', type: dynamodb.AttributeType.STRING }}, sortKey: {{ name: 'SK', type: dynamodb.AttributeType.STRING }}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, encryption: dynamodb.TableEncryption.AWS_MANAGED, pointInTimeRecovery: true, stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES, removalPolicy: cdk.RemovalPolicy.RETAIN, }}); // Cognito User Pool const userPool = new cognito.UserPool(this, '{self.app_name}UserPool', {{ userPoolName: `${{cdk.Stack.of(this).stackName}}-users`, selfSignUpEnabled: true, signInAliases: {{ email: true }}, autoVerify: {{ email: true }}, passwordPolicy: {{ minLength: 8, requireLowercase: true, requireUppercase: true, requireDigits: true, requireSymbols: false, }}, mfa: cognito.Mfa.OPTIONAL, mfaSecondFactor: {{ sms: false, otp: true, }}, removalPolicy: cdk.RemovalPolicy.RETAIN, }}); const userPoolClient = userPool.addClient('{self.app_name}Client', {{ authFlows: {{ userSrp: true, }}, accessTokenValidity: cdk.Duration.hours(1), refreshTokenValidity: cdk.Duration.days(30), }}); // Lambda Function const apiFunction = new lambda.Function(this, '{self.app_name}ApiFunction', {{ functionName: `${{cdk.Stack.of(this).stackName}}-api`, runtime: lambda.Runtime.NODEJS_18_X, handler: 'index.handler', code: lambda.Code.fromAsset('./src'), memorySize: 512, timeout: cdk.Duration.seconds(10), environment: {{ TABLE_NAME: table.tableName, USER_POOL_ID: userPool.userPoolId, }}, logRetention: 7, // days }}); // Grant Lambda permissions to DynamoDB table.grantReadWriteData(apiFunction); // API Gateway const api = new apigateway.RestApi(this, '{self.app_name}Api', {{ restApiName: `${{cdk.Stack.of(this).stackName}}-api`, description: 'API for {self.app_name}', defaultCorsPreflightOptions: {{ allowOrigins: apigateway.Cors.ALL_ORIGINS, allowMethods: apigateway.Cors.ALL_METHODS, allowHeaders: ['Content-Type', 'Authorization'], }}, deployOptions: {{ stageName: 'prod', throttlingRateLimit: 100, throttlingBurstLimit: 200, metricsEnabled: true, loggingLevel: apigateway.MethodLoggingLevel.INFO, }}, }}); // Cognito Authorizer const authorizer = new apigateway.CognitoUserPoolsAuthorizer(this, 'ApiAuthorizer', {{ cognitoUserPools: [userPool], }}); // API Integration const integration = new apigateway.LambdaIntegration(apiFunction); // Add proxy resource (/{{proxy+}}) const proxyResource = api.root.addProxy({{ defaultIntegration: integration, anyMethod: true, defaultMethodOptions: {{ authorizer: authorizer, authorizationType: apigateway.AuthorizationType.COGNITO, }}, }}); // Outputs new cdk.CfnOutput(this, 'ApiUrl', {{ value: api.url, description: 'API Gateway URL', }}); new cdk.CfnOutput(this, 'UserPoolId', {{ value: userPool.userPoolId, description: 'Cognito User Pool ID', }}); new cdk.CfnOutput(this, 'UserPoolClientId', {{ value: userPoolClient.userPoolClientId, description: 'Cognito User Pool Client ID', }}); new cdk.CfnOutput(this, 'TableName', {{ value: table.tableName, description: 'DynamoDB Table Name', }}); }} }} """ return stack def generate_terraform_configuration(self) -> str: """ Generate Terraform configuration for serverless stack. Returns: Terraform HCL configuration as string """ terraform = f"""terraform {{ required_version = ">= 1.0" required_providers {{ aws = {{ source = "hashicorp/aws" version = "~> 5.0" }} }} }} provider "aws" {{ region = var.aws_region }} variable "aws_region" {{ description = "AWS region" type = string default = "{self.region}" }} variable "environment" {{ description = "Environment name" type = string default = "dev" }} variable "app_name" {{ description = "Application name" type = string default = "{self.app_name}" }} # DynamoDB Table resource "aws_dynamodb_table" "main" {{ name = "${{var.environment}}-${{var.app_name}}-data" billing_mode = "PAY_PER_REQUEST" hash_key = "PK" range_key = "SK" attribute {{ name = "PK" type = "S" }} attribute {{ name = "SK" type = "S" }} server_side_encryption {{ enabled = true }} point_in_time_recovery {{ enabled = true }} stream_enabled = true stream_view_type = "NEW_AND_OLD_IMAGES" tags = {{ Environment = var.environment Application = var.app_name }} }} # Cognito User Pool resource "aws_cognito_user_pool" "main" {{ name = "${{var.environment}}-${{var.app_name}}-users" username_attributes = ["email"] auto_verified_attributes = ["email"] password_policy {{ minimum_length = 8 require_lowercase = true require_numbers = true require_uppercase = true require_symbols = false }} mfa_configuration = "OPTIONAL" software_token_mfa_configuration {{ enabled = true }} schema {{ name = "email" attribute_data_type = "String" required = true mutable = true }} tags = {{ Environment = var.environment Application = var.app_name }} }} resource "aws_cognito_user_pool_client" "main" {{ name = "${{var.environment}}-${{var.app_name}}-client" user_pool_id = aws_cognito_user_pool.main.id generate_secret = false explicit_auth_flows = [ "ALLOW_USER_SRP_AUTH", "ALLOW_REFRESH_TOKEN_AUTH" ] refresh_token_validity = 30 access_token_validity = 1 id_token_validity = 1 token_validity_units {{ refresh_token = "days" access_token = "hours" id_token = "hours" }} }} # IAM Role for Lambda resource "aws_iam_role" "lambda" {{ name = "${{var.environment}}-${{var.app_name}}-lambda-role" assume_role_policy = jsonencode({{ Version = "2012-10-17" Statement = [{{ Action = "sts:AssumeRole" Effect = "Allow" Principal = {{ Service = "lambda.amazonaws.com" }} }}] }}) tags = {{ Environment = var.environment Application = var.app_name }} }} resource "aws_iam_role_policy_attachment" "lambda_basic" {{ role = aws_iam_role.lambda.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" }} resource "aws_iam_role_policy" "dynamodb" {{ name = "dynamodb-access" role = aws_iam_role.lambda.id policy = jsonencode({{ Version = "2012-10-17" Statement = [{{ Effect = "Allow" Action = [ "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:Query", "dynamodb:Scan" ] Resource = aws_dynamodb_table.main.arn }}] }}) }} # Lambda Function resource "aws_lambda_function" "api" {{ filename = "lambda.zip" function_name = "${{var.environment}}-${{var.app_name}}-api" role = aws_iam_role.lambda.arn handler = "index.handler" runtime = "nodejs18.x" memory_size = 512 timeout = 10 environment {{ variables = {{ TABLE_NAME = aws_dynamodb_table.main.name USER_POOL_ID = aws_cognito_user_pool.main.id ENVIRONMENT = var.environment }} }} tags = {{ Environment = var.environment Application = var.app_name }} }} # CloudWatch Log Group resource "aws_cloudwatch_log_group" "lambda" {{ name = "/aws/lambda/${{aws_lambda_function.api.function_name}}" retention_in_days = 7 tags = {{ Environment = var.environment Application = var.app_name }} }} # API Gateway resource "aws_api_gateway_rest_api" "main" {{ name = "${{var.environment}}-${{var.app_name}}-api" description = "API for ${{var.app_name}}" tags = {{ Environment = var.environment Application = var.app_name }} }} resource "aws_api_gateway_authorizer" "cognito" {{ name = "cognito-authorizer" rest_api_id = aws_api_gateway_rest_api.main.id type = "COGNITO_USER_POOLS" provider_arns = [aws_cognito_user_pool.main.arn] }} resource "aws_api_gateway_resource" "proxy" {{ rest_api_id = aws_api_gateway_rest_api.main.id parent_id = aws_api_gateway_rest_api.main.root_resource_id path_part = "{{proxy+}}" }} resource "aws_api_gateway_method" "proxy" {{ rest_api_id = aws_api_gateway_rest_api.main.id resource_id = aws_api_gateway_resource.proxy.id http_method = "ANY" authorization = "COGNITO_USER_POOLS" authorizer_id = aws_api_gateway_authorizer.cognito.id }} resource "aws_api_gateway_integration" "lambda" {{ rest_api_id = aws_api_gateway_rest_api.main.id resource_id = aws_api_gateway_resource.proxy.id http_method = aws_api_gateway_method.proxy.http_method integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.api.invoke_arn }} resource "aws_lambda_permission" "apigw" {{ statement_id = "AllowAPIGatewayInvoke" action = "lambda:InvokeFunction" function_name = aws_lambda_function.api.function_name principal = "apigateway.amazonaws.com" source_arn = "${{aws_api_gateway_rest_api.main.execution_arn}}/*/*" }} resource "aws_api_gateway_deployment" "main" {{ depends_on = [ aws_api_gateway_integration.lambda ] rest_api_id = aws_api_gateway_rest_api.main.id stage_name = var.environment }} # Outputs output "api_url" {{ description = "API Gateway URL" value = aws_api_gateway_deployment.main.invoke_url }} output "user_pool_id" {{ description = "Cognito User Pool ID" value = aws_cognito_user_pool.main.id }} output "user_pool_client_id" {{ description = "Cognito User Pool Client ID" value = aws_cognito_user_pool_client.main.id }} output "table_name" {{ description = "DynamoDB Table Name" value = aws_dynamodb_table.main.name }} """ return terraform