Lambda Function que recebe eventos de pipelines AWS CodePipeline via EventBridge e envia notificações formatadas para webhooks do Discord.
Esta função é agnóstica a pipelines específicos. O webhook Discord é injetado no evento pelo EventBridge via Input Transformer configurado no Terraform de cada pipeline.
CodePipeline → EventBridge (injeta webhook_url) → Lambda → Discord Webhook
- Pipeline do CodePipeline muda de estado (SUCCEEDED, FAILED, etc)
- EventBridge captura o evento
- Input Transformer injeta
webhook_urlno evento (configurado via Terraform) - Lambda recebe evento, formata mensagem e envia para Discord
- Discord exibe notificação com embed colorido
A Lambda espera eventos do EventBridge com a seguinte estrutura:
{
"detail": {
"pipeline": "nome-do-pipeline",
"state": "SUCCEEDED",
"execution-id": "abc-123-xyz",
"webhook_url": "https://discord.com/api/webhooks/..."
},
"time": "2025-11-18T12:00:00Z"
}Atributo webhook_url: Injetado pelo Terraform via EventBridge Input Transformer.
variable "discord_webhook_url" {
description = "Discord webhook URL for pipeline notifications"
type = string
sensitive = true
}
variable "pipeline_name" {
description = "Nome do pipeline CodePipeline"
type = string
}
variable "discord_lambda_arn" {
description = "ARN da Lambda function-notify-discord"
type = string
}
# Regra do EventBridge
resource "aws_cloudwatch_event_rule" "pipeline_notifications" {
name = "discord-notify-${var.pipeline_name}"
description = "Notifica Discord sobre mudanças no pipeline ${var.pipeline_name}"
event_pattern = jsonencode({
source = ["aws.codepipeline"]
detail-type = ["CodePipeline Pipeline Execution State Change"]
detail = {
pipeline = [var.pipeline_name]
}
})
}
# Target com Input Transformer - INJETA O WEBHOOK
resource "aws_cloudwatch_event_target" "discord_lambda" {
rule = aws_cloudwatch_event_rule.pipeline_notifications.name
arn = var.discord_lambda_arn
input_transformer {
input_paths = {
pipeline = "$.detail.pipeline"
state = "$.detail.state"
execution_id = "$.detail.execution-id"
time = "$.time"
region = "$.region"
account = "$.account"
}
input_template = <<-JSON
{
"detail": {
"pipeline": <pipeline>,
"state": <state>,
"execution-id": <execution_id>,
"webhook_url": "${var.discord_webhook_url}",
"region": <region>,
"account": <account>
},
"time": <time>
}
JSON
}
}
# Permissão para EventBridge invocar Lambda
resource "aws_lambda_permission" "allow_eventbridge" {
statement_id = "AllowExecutionFromEventBridge-${var.pipeline_name}"
action = "lambda:InvokeFunction"
function_name = var.discord_lambda_arn
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.pipeline_notifications.arn
}pip install -r requirements.txt.
├── lambda_function.py # Handler da Lambda
├── discord_notify.py # Lógica de notificação Discord
├── logger.py # Sistema de logging estruturado
├── requirements.txt # Dependências Python
├── event-test.json # Evento mock para testes
├── CLAUDE.md # Documentação para Claude Code
└── README.md # Este arquivo
O empacotamento é feito automaticamente pelo AWS CodeBuild via buildspec.yml:
# buildspec.yml
build:
commands:
- pip install -r requirements.txt -t ./package
- cp lambda_function.py discord_notify.py logger.py ./package/
- cd package && zip -r ../function.zip . && cd ..O que o build faz:
- Instala dependências Python (requests) na pasta
./package - Copia código da aplicação para
./package - Cria
function.zipcom código + dependências - Artefato disponibilizado para deploy
Arquivos gerados (não versionados no Git):
function.zip- Pacote finalpackage/- Pasta temporária de build
Deploy manual (desenvolvimento):
# Instalar dependências
pip install -r requirements.txt -t ./package
# Copiar código
cp lambda_function.py discord_notify.py logger.py ./package/
# Criar pacote
cd package && zip -r ../function.zip . && cd ..
# Deploy via AWS CLI
aws lambda update-function-code \
--function-name function-notify-discord \
--zip-file fileb://function.zip
# Limpar
rm -rf package/ function.zipA Lambda requer apenas uma variável de ambiente:
| Variável | Descrição | Obrigatório |
|---|---|---|
WEBHOOK_TEST |
Webhook Discord de fallback caso webhook_url não venha no evento |
Sim |
As mensagens são enviadas como embeds Discord com cores e emojis baseados no estado do pipeline:
| Estado | Cor | Emoji | Código |
|---|---|---|---|
| SUCCEEDED | Verde | ✅ | 1673044 |
| FAILED | Vermelho | ❌ | 16724787 |
| STARTED | Azul | 🚀 | 3447003 |
| STOPPED | Laranja | 15258703 |
|
| SUPERSEDED | Cinza | 🔄 | 9807270 |
| Outros | Laranja | ℹ️ | 15258703 |
{
"content": "✅ Pipeline succeeded",
"embeds": [{
"title": "ppl-gt-api-perito-prd",
"description": "Estado do pipeline alterado para **SUCCEEDED**",
"color": 1673044,
"fields": [
{
"name": "ID da Execução",
"value": "`abc-123-xyz`",
"inline": false
},
{
"name": "Status",
"value": "**SUCCEEDED**",
"inline": true
}
],
"footer": {
"text": "AWS CodePipeline Notification"
},
"timestamp": "2025-11-18T12:00:00Z"
}]
}Um arquivo de teste está disponível em event-test.json:
{
"detail": {
"pipeline": "ppl-teste",
"state": "SUCCEEDED",
"execution-id": "test-123-abc-xyz",
"webhook_url": "https://discord.com/api/webhooks/SEU_WEBHOOK_AQUI",
"region": "us-east-1",
"account": "123456789012"
},
"time": "2025-11-18T12:00:00Z"
}import json
from lambda_function import lambda_handler
class MockContext:
aws_request_id = "test-request-id-123"
function_name = "function-notify-discord"
function_version = "$LATEST"
memory_limit_in_mb = 128
def get_remaining_time_in_millis(self):
return 300000
with open('event-test.json') as f:
event = json.load(f)
lambda_handler(event, MockContext())Você pode testar a Lambda diretamente na AWS usando o arquivo de teste:
# Edite event-test.json e adicione seu webhook Discord
# Depois invoque a Lambda:
aws lambda invoke \
--function-name function-notify-discord \
--payload file://event-test.json \
--cli-binary-format raw-in-base64-out \
response.json
# Ver resposta
cat response.jsonVocê pode modificar o campo state no event-test.json para testar diferentes cores:
"state": "SUCCEEDED"- Mensagem verde"state": "FAILED"- Mensagem vermelha"state": "STARTED"- Mensagem laranja
A Lambda utiliza logging estruturado em JSON otimizado para CloudWatch Logs Insights.
- Formato JSON para todos os logs
- lambda_request_id automático em todos os logs
- Timestamps UTC no formato ISO 8601
- Stack traces completos em exceções
- Contexto estruturado via
extra={} - Redução de ruído (boto3, urllib3, requests em nível WARNING)
- Métricas de performance (tempo de execução)
{
"timestamp": "2025-11-18T12:34:56.789Z",
"level": "INFO",
"logger": "discord_notify",
"message": "Pipeline identificado",
"module": "discord_notify",
"function": "parse_event",
"line": 35,
"lambda_request_id": "abc-123-def-456",
"pipeline": "ppl-gt-api-perito-prd",
"execution_id": "exec-123",
"state": "SUCCEEDED"
}Início da execução:
{
"message": "Lambda invocada",
"function_name": "function-notify-discord",
"function_version": "$LATEST",
"request_id": "abc-123-def-456",
"memory_limit_mb": 128,
"remaining_time_ms": 299000
}Processamento:
{
"message": "Pipeline identificado",
"pipeline": "ppl-gt-api-perito-prd",
"execution_id": "exec-abc-123",
"state": "SUCCEEDED"
}Sucesso:
{
"message": "Notificação enviada com sucesso",
"status_code": 204,
"execution_time_seconds": 0.45,
"pipeline": "ppl-gt-api-perito-prd",
"state": "SUCCEEDED"
}Erro:
{
"level": "ERROR",
"message": "Erro ao enviar notificação Discord: Connection timeout",
"error_type": "RequestException",
"exception": "Traceback...",
"pipeline": "ppl-gt-api-perito-prd",
"state": "FAILED"
}Filtrar por pipeline:
fields @timestamp, message, state, execution_time_seconds
| filter pipeline = "ppl-gt-api-perito-prd"
| sort @timestamp descAnalisar erros:
fields @timestamp, message, error_type, exception
| filter level = "ERROR"
| stats count() by error_typeMétricas de performance:
fields @timestamp, execution_time_seconds, pipeline, state
| filter ispresent(execution_time_seconds)
| stats avg(execution_time_seconds), max(execution_time_seconds) by pipelineRastrear execução específica:
fields @timestamp, message, function, line
| filter lambda_request_id = "abc-123-def-456"
| sort @timestamp asc- Lambda agnóstica - não conhece pipelines específicos
- Escalável - adicionar pipeline = só Terraform, zero mudança na Lambda
- Manutenível - webhook gerenciado no Terraform junto com o pipeline
- Testável - evento mock simples para testes
- Sem hardcode - nenhuma lógica condicional baseada em nomes de pipeline
- Verificar EventBridge Rule está ativa
- Verificar permissão
aws_lambda_permissionestá configurada - Verificar logs do CloudWatch da Lambda
- Verificar
webhook_urlestá correto no evento (logs da Lambda) - Testar webhook manualmente com curl:
curl -X POST "WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d '{"content": "Teste"}'
- Verificar webhook não foi deletado/revogado no Discord
- Verificar Input Transformer do EventBridge está injetando
webhook_url - Verificar logs da Lambda para ver estrutura do evento recebido
- Conferir que
webhook_urlestá sendo passado corretamente noinput_template
Projeto interno GuideTech.