diff --git a/.github/workflows/workflow_build.yaml b/.github/workflows/workflow_build.yaml index a90ee74..bee0603 100644 --- a/.github/workflows/workflow_build.yaml +++ b/.github/workflows/workflow_build.yaml @@ -1,4 +1,4 @@ -name: Publish Docker Image to AWS ECR Private +name: Deploy Lambda ZIP on: workflow_dispatch: @@ -7,7 +7,7 @@ on: - main jobs: build: - name: Build and push Docker image + name: Package and deploy Lambda ZIP runs-on: ubuntu-latest environment: certified-builder-py steps: @@ -22,25 +22,37 @@ jobs: aws-region: us-east-1 audience: sts.amazonaws.com - - name: Login to Amazon ECR Private - run: aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com - - - name: Build Docker image + - name: Prepare package directories run: | - docker build -t ${{ secrets.ECR_REPOSITORY_BUILDER }}:latest . - docker tag ${{ secrets.ECR_REPOSITORY_BUILDER }}:latest ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ secrets.ECR_REPOSITORY_BUILDER }}:latest - - - name: Push Docker image + rm -rf dist + mkdir -p dist/package + + - name: Build Lambda ZIP run: | - docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ secrets.ECR_REPOSITORY_BUILDER }}:latest + docker run --rm \ + -v "$PWD:/work" \ + -w /work \ + python:3.13-slim \ + bash -lc ' + set -euo pipefail + apt-get update >/dev/null + apt-get install -y zip >/dev/null + python -m pip install --upgrade pip >/dev/null + python -m pip install --no-cache-dir -r requirements.txt -t dist/package >/dev/null + cp -R aws certified_builder models dist/package/ + cp lambda_function.py config.py requirements.txt dist/package/ + cd dist/package + zip -qr ../lambda.zip . + ' - - name: Update Lambda function + - name: Deploy Lambda ZIP run: | aws lambda update-function-code \ - --function-name tech-floripa-certified-builder-dev \ - --image-uri ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ secrets.ECR_REPOSITORY_BUILDER }}:latest - + --function-name tech-floripa-certificates-builder-dev \ + --zip-file fileb://dist/lambda.zip + aws lambda wait function-updated \ + --function-name tech-floripa-certificates-builder-dev + - name: Complete run: | - echo "Docker image has been pushed to AWS ECR Private and Lambda function has been updated" - + echo "Lambda ZIP deployed successfully" diff --git a/Dockerfile b/Dockerfile index 30a1384..623b62d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,36 +1,36 @@ -FROM public.ecr.aws/lambda/python:3.13 - -# Install system dependencies -RUN dnf update -y && \ - dnf install -y \ - freetype-devel \ - libjpeg-turbo-devel \ - zlib-devel \ - gcc \ - make \ - python3-devel \ - fontconfig && \ - dnf clean all - -# Set working directory -WORKDIR ${LAMBDA_TASK_ROOT} - -# Copy requirements first to leverage Docker cache +FROM python:3.13-slim + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential \ + curl \ + fontconfig \ + libfreetype6-dev \ + libjpeg62-turbo-dev \ + zlib1g-dev && \ + rm -rf /var/lib/apt/lists/* + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=/var/task +ENV FONTCONFIG_PATH=/etc/fonts +ENV AWS_LAMBDA_RUNTIME_API="" + +WORKDIR /var/task + COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt +RUN pip install --no-cache-dir awslambdaric + +ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/local/bin/aws-lambda-rie +RUN chmod +x /usr/local/bin/aws-lambda-rie -# Copy the entire application COPY . . +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh -# Create necessary directories RUN mkdir -p /tmp/certificates && \ chmod 777 /tmp/certificates -# Set environment variables -ENV PYTHONPATH=${LAMBDA_TASK_ROOT} -ENV FONTCONFIG_PATH=/etc/fonts -ENV PYTHONDONTWRITEBYTECODE=1 -ENV PYTHONUNBUFFERED=1 - -# Set the CMD to your handler +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] CMD [ "lambda_function.lambda_handler" ] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..6f87689 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -eu + +handler="${1:-lambda_function.lambda_handler}" + +if [ -z "${AWS_LAMBDA_RUNTIME_API:-}" ]; then + exec /usr/local/bin/aws-lambda-rie python -m awslambdaric "$handler" +fi + +exec python -m awslambdaric "$handler" diff --git a/lambda_function.py b/lambda_function.py index 2d5253a..7c10560 100644 --- a/lambda_function.py +++ b/lambda_function.py @@ -71,16 +71,6 @@ def create_participant_object(participant_data): if participant_data.get("time_checkin") else None ), - checkin_latitude=( - float(participant_data.get("checkin_latitude")) - if participant_data.get("checkin_latitude") - else None - ), - checkin_longitude=( - float(participant_data.get("checkin_longitude")) - if participant_data.get("checkin_longitude") - else None - ), ) # Create Participant object @@ -88,8 +78,6 @@ def create_participant_object(participant_data): first_name=participant_data.get("first_name"), last_name=participant_data.get("last_name"), email=participant_data.get("email"), - phone=participant_data.get("phone"), - cpf=participant_data.get("cpf", ""), certificate=certificate, event=event, ) diff --git a/models/event.py b/models/event.py index 16a0818..320c28e 100644 --- a/models/event.py +++ b/models/event.py @@ -9,5 +9,3 @@ class Event(BaseModel): product_name: str date: datetime time_checkin: Optional[datetime] = None - checkin_latitude: Optional[float] = None - checkin_longitude: Optional[float] = None diff --git a/models/participant.py b/models/participant.py index c98cce9..9252261 100644 --- a/models/participant.py +++ b/models/participant.py @@ -17,8 +17,6 @@ class Participant(BaseModel): first_name: str last_name: str email: EmailStr - phone: str - cpf: str validation_code: Optional[str] = Field( default_factory=lambda: "".join(random.choices(string.hexdigits, k=9)), init=False, diff --git a/readme.md b/readme.md index f989139..1954a3d 100644 --- a/readme.md +++ b/readme.md @@ -13,8 +13,7 @@ Sistema de geração automática de certificados para eventos usando AWS Lambda - Registro na blockchain Solana para autenticação - Processamento de mensagens SQS - Execução em container Docker -- Deploy automatizado para AWS Lambda -- Integração com AWS ECR +- Deploy automatizado para AWS Lambda via ZIP package - Envio de mensagens para fila de notificação com dados do certificado ## Estrutura do Projeto @@ -55,7 +54,6 @@ project_root/ - qrcode (Geração de QR codes) - Docker - AWS Lambda -- AWS ECR - AWS SQS - [Solana Blockchain (Registro de certificados)](https://github.com/p4ndabk/certificates-on-solana) @@ -142,7 +140,7 @@ pip install -r requirements.txt 3. Execute com Docker: ```bash -docker build -t certified-builder . && docker run -p 9000:8080 certified-builder +docker compose up --build ``` 4. Teste localmente: @@ -155,9 +153,9 @@ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d O deploy é automatizado através do GitHub Actions: 1. Push para a branch main dispara o workflow -2. Imagem Docker é construída -3. Upload para AWS ECR -4. Atualização da função Lambda +2. Um pacote ZIP compatível com Lambda é gerado em ambiente Linux +3. O workflow executa `aws lambda update-function-code` +4. A função `tech-floripa-certificates-builder-dev` recebe o novo código ## Estrutura do Certificado Gerado @@ -181,4 +179,3 @@ O deploy é automatizado através do GitHub Actions: ## Licença Este projeto está sob a licença MIT. Veja o arquivo `LICENSE` para mais detalhes. - diff --git a/tests/test_certified_builder.py b/tests/test_certified_builder.py index e18c3c4..a9765cc 100644 --- a/tests/test_certified_builder.py +++ b/tests/test_certified_builder.py @@ -27,8 +27,6 @@ def mock_event(): product_name="Evento de Teste", date=datetime.strptime("2025-03-26 20:55:25", "%Y-%m-%d %H:%M:%S"), time_checkin=datetime.strptime("2025-03-26 20:55:44", "%Y-%m-%d %H:%M:%S"), - checkin_latitude=-27.5460492, - checkin_longitude=-48.6227075, ) @@ -38,8 +36,6 @@ def mock_participant(mock_certificate, mock_event): first_name="Jardel", last_name="Godinho", email="jardelgodinho@gmail.com", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=mock_event, ) @@ -451,8 +447,6 @@ def test_build_certificates_with_multiple_participants_same_resources( first_name="Maria", last_name="Silva", email="maria@example.com", - phone="(48) 99999-9999", - cpf="111.111.111-11", certificate=mock_participant.certificate, event=mock_participant.event, ) @@ -507,8 +501,6 @@ def test_build_certificates_with_different_backgrounds( first_name="João", last_name="Santos", email="joao@example.com", - phone="(48) 88888-8888", - cpf="222.222.222-22", certificate=second_certificate, event=mock_participant.event, ) @@ -627,16 +619,12 @@ def test_generate_certificate_with_long_name( product_name="Evento Teste", date=datetime.now(), time_checkin=datetime.now(), - checkin_latitude=0.0, - checkin_longitude=0.0, ) long_name_participant = Participant( first_name="João Pedro", last_name="da Silva Santos Oliveira", email="joao@example.com", - phone="(48) 99999-9999", - cpf="123.456.789-00", certificate=mock_certificate, event=mock_event, ) diff --git a/tests/test_participant.py b/tests/test_participant.py index 2c9bd34..e2a4ef8 100644 --- a/tests/test_participant.py +++ b/tests/test_participant.py @@ -23,8 +23,6 @@ def mock_event(): product_name="Evento de Teste", date=datetime.strptime("2025-03-26 20:55:25", "%Y-%m-%d %H:%M:%S"), time_checkin=datetime.strptime("2025-03-26 20:55:44", "%Y-%m-%d %H:%M:%S"), - checkin_latitude=-27.5460492, - checkin_longitude=-48.6227075, ) @@ -33,8 +31,6 @@ def test_participant_initialization(mock_certificate, mock_event): first_name="Jardel", last_name="Godinho", email="jardelgodinho@gmail.com", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=mock_event, ) @@ -42,8 +38,6 @@ def test_participant_initialization(mock_certificate, mock_event): assert participant.first_name == "Jardel" assert participant.last_name == "Godinho" assert participant.email == "jardelgodinho@gmail.com" - assert participant.phone == "(48) 98866-7447" - assert participant.cpf == "000.000.000-00" assert participant.certificate == mock_certificate assert participant.event == mock_event assert len(participant.validation_code) == 9 @@ -54,8 +48,6 @@ def test_name_completed(mock_certificate, mock_event): first_name="Jardel", last_name="Godinho", email="jardelgodinho@gmail.com", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=mock_event, ) @@ -67,8 +59,6 @@ def test_formated_validation_code(mock_certificate, mock_event): first_name="Jardel", last_name="Godinho", email="jardelgodinho@gmail.com", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=mock_event, ) @@ -83,8 +73,6 @@ def test_create_name_certificate(mock_certificate, mock_event): first_name="Jardel", last_name="Godinho", email="jardelgodinho@gmail.com", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=mock_event, ) @@ -101,8 +89,6 @@ def test_invalid_email(mock_certificate, mock_event): first_name="Jardel", last_name="Godinho", email="invalid-email", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=mock_event, ) @@ -114,8 +100,6 @@ def test_missing_required_field(mock_certificate, mock_event): Participant( first_name="Jardel", email="jardelgodinho@gmail.com", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=mock_event, ) @@ -126,8 +110,6 @@ def test_name_completed_with_multiple_names(mock_certificate, mock_event): first_name="Jardel Silva", last_name="Godinho Santos", email="jardelgodinho@gmail.com", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=mock_event, ) @@ -145,16 +127,12 @@ def test_sanitize_filename_special_characters(mock_certificate): product_name="87º Python Floripa × CODECON @ UNICESUSC", date=datetime.strptime("2025-03-26 20:55:25", "%Y-%m-%d %H:%M:%S"), time_checkin=datetime.strptime("2025-03-26 20:55:44", "%Y-%m-%d %H:%M:%S"), - checkin_latitude=-27.5460492, - checkin_longitude=-48.6227075, ) participant = Participant( first_name="Rodrigo", last_name="Farah", email="rodrigo@example.com", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=event_with_special_chars, ) @@ -180,16 +158,12 @@ def test_create_name_certificate_with_special_characters(mock_certificate): product_name="87º Python Floripa × CODECON @ UNICESUSC", date=datetime.strptime("2025-06-19 11:15:31", "%Y-%m-%d %H:%M:%S"), time_checkin=datetime.strptime("2025-06-19 11:15:31", "%Y-%m-%d %H:%M:%S"), - checkin_latitude=-27.5460492, - checkin_longitude=-48.6227075, ) participant = Participant( first_name="Rodrigo", last_name="Farah", email="rodrigo.farah@example.com", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=event_with_special_chars, ) @@ -221,8 +195,6 @@ def test_sanitize_filename_edge_cases(mock_certificate, mock_event): first_name="Test", last_name="User", email="test@example.com", - phone="(48) 98866-7447", - cpf="000.000.000-00", certificate=mock_certificate, event=mock_event, )