# Lambda Starter Template Remaining SAM scaffold for an existing Python Lambda stack. New functions belong in an HCP Terraform repo; see [terraform-project-layout.md](terraform-project-layout.md) and [aws-infrastructure.md](aws-infrastructure.md#lambda-defaults). ## Project Structure ``` my-stack/ ├── template.yaml ├── samconfig.toml.example ├── src/ │ └── handler/ │ ├── app.py │ └── requirements.txt └── .gitignore ``` See [sam-project-layout.md](sam-project-layout.md) for the full directory convention. ## template.yaml ```yaml AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: my-stack — one-line purpose Globals: Function: Runtime: python3.12 Architecture: arm64 Timeout: 30 MemorySize: 256 LoggingConfig: LogFormat: JSON Resources: HandlerFunction: Type: AWS::Serverless::Function Properties: FunctionName: my-stack-handler CodeUri: src/handler/ Handler: app.handler Environment: Variables: CONFIG_SECRET: my-stack/config Policies: - AWSLambdaBasicExecutionRole - Statement: - Effect: Allow Action: secretsmanager:GetSecretValue Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:my-stack/* HandlerLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${HandlerFunction} RetentionInDays: 60 Outputs: HandlerArn: Description: Handler Lambda ARN Value: !GetAtt HandlerFunction.Arn ``` Key points: - `Globals.Function` sets runtime, architecture, and JSON logging once for the whole template. - The `LogGroup` is declared **explicitly** with `RetentionInDays: 60`. Omit it and CloudWatch creates the log group on first invocation with no retention — logs accumulate forever. - IAM scopes `secretsmanager:GetSecretValue` to the stack's secret prefix only. Add specific permissions as needed; never use `AdministratorAccess`. ## src/handler/app.py ```python import json import os import boto3 _secrets_client = boto3.client("secretsmanager") _config = None def _get_config(): global _config if _config is None: resp = _secrets_client.get_secret_value(SecretId=os.environ["CONFIG_SECRET"]) _config = json.loads(resp["SecretString"]) return _config def handler(event, context): config = _get_config() # ... your logic ... return {"statusCode": 200, "body": json.dumps({"ok": True})} ``` The module-level `_config` global caches the secret across warm invocations. The first call per cold start hits Secrets Manager; subsequent calls reuse the cached value. See [secrets-and-config.md](secrets-and-config.md) for the rationale. ## src/handler/requirements.txt Keep this file in every function directory even when empty — SAM looks for it during `sam build`. ``` # Per-function dependencies. Leave empty if the function uses only boto3 and stdlib. ``` ## Naming Reminders - `FunctionName` must be kebab-case and start with the stack name (`my-stack-handler`). - Secret IDs use `stack-name/secret-name`. - Stack name itself is set in `samconfig.toml`, not the template — match the repo name. See [naming-conventions.md](naming-conventions.md).