Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions .github/workflows/CD.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,65 @@

name: CD

on:
push:
branches:
- main
- dev
- homolog
- prod

workflow_dispatch:

jobs:
DeployToAWS:
environment:
name: ${{ github.ref_name }}
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Python 3.13
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Set AWS Account ID and other variables
run: |
if [[ "${{ github.ref_name }}" == "dev" ]]; then
echo "AWS_ACCOUNT_ID=${{ secrets.AWS_ACCOUNT_ID_DEV }}" >> $GITHUB_ENV
elif [[ "${{ github.ref_name }}" == "homolog" ]]; then
echo "AWS_ACCOUNT_ID=${{ secrets.AWS_ACCOUNT_ID_HOML }}" >> $GITHUB_ENV
elif [[ "${{ github.ref_name }}" == "prod" ]]; then
echo "AWS_ACCOUNT_ID=${{ secrets.AWS_ACCOUNT_ID_PROD }}" >> $GITHUB_ENV
else
echo "Invalid branch name!" && exit 1
fi
echo "STACK_NAME=PortfolioStackBack${{github.ref_name}}" >> $GITHUB_ENV

- name: Setup AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ vars.AWS_REGION }}
role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/GithubActionsRole
role-session-name: github-action

- name: Installing Dependencies
run: |
npm install -g aws-cdk
cd iac
pip install -r requirements-infra.txt

- name: DeployWithCDK
run: |
cd iac
cdk synth
cdk deploy --require-approval never
env:
AWS_ACCOUNT_ID: ${{ env.AWS_ACCOUNT_ID }}
AWS_REGION: ${{ vars.AWS_REGION }}
STACK_NAME: ${{ env.STACK_NAME }}
GITHUB_REF_NAME: ${{ github.ref_name }}
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ on:

jobs:
testApp:
uses: maua-dev/ci_workflows_reusable/.github/workflows/pytest_ci.yml@V1.0
uses: maua-dev/ci_workflows_reusable/.github/workflows/pytest_ci_313.yml@V1.3
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# clean_mss_template 🌡🍽
# clean_mss_template 🌡🍽

Template for microservices repositories based in Clean Arch

Expand Down
84 changes: 50 additions & 34 deletions iac/adjust_layer_directory.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,53 @@
import os
import shutil
from pathlib import Path
import sys

IAC_DIRECTORY_NAME = "iac"
SOURCE_DIRECTORY_NAME = "src"
LAMBDA_LAYER_PREFIX = os.path.join("python", "src")


def adjust_layer_directory(shared_dir_name: str, destination: str):
# Get the root directory of the source directory
root_directory = Path(__file__).parent.parent
iac_directory = os.path.join(root_directory, IAC_DIRECTORY_NAME)

print(f"Root directory: {root_directory}")
print(f"Root direcotry files: {os.listdir(root_directory)}")
print(f"IaC directory: {iac_directory}")
print(f"IaC directory files: {os.listdir(iac_directory)}")


# Get the destination and source directory
destination_directory = os.path.join(root_directory, IAC_DIRECTORY_NAME, destination)
source_directory = os.path.join(root_directory, SOURCE_DIRECTORY_NAME, shared_dir_name)

# Delete the destination directory if it exists
if os.path.exists(destination_directory):
shutil.rmtree(destination_directory)

# Copy the source directory to the destination directory
shutil.copytree(source_directory, os.path.join(destination_directory, LAMBDA_LAYER_PREFIX, shared_dir_name))
print(
f"Copying files from {source_directory} to {os.path.join(destination_directory, LAMBDA_LAYER_PREFIX, shared_dir_name)}")


import subprocess
from pathlib import Path

BUILD_DIRECTORY = "build"
PYTHON_TOP_LEVEL_DIR = os.path.join(BUILD_DIRECTORY, "python")
REQUIREMENTS_FILE = "requirements-app.txt"

PROJECT_ROOT = Path(__file__).parent.parent
SHARED_CODE_SOURCE = os.path.join(PROJECT_ROOT, "src", "shared")


def adjust_layer_directory():
"""
Prepara um diretório 'build' para uma Lambda Layer do AWS CDK.

A função junta o código local compartilhado e as dependências externas (pip)
na estrutura de pastas que a Lambda espera (/python).
"""

# Garante que o build seja sempre limpo, removendo qualquer artefato antigo.
if os.path.exists(BUILD_DIRECTORY):
shutil.rmtree(BUILD_DIRECTORY)

# Cria a estrutura de pastas 'build/python/src/'.
# Isso é necessário para que os imports 'from src.shared...' funcionem na Lambda.
shared_code_intermediate_dir = os.path.join(PYTHON_TOP_LEVEL_DIR, "src")
os.makedirs(shared_code_intermediate_dir)

# Copia o código compartilhado (de 'src/shared') para dentro da estrutura da Layer.
# O resultado final será 'build/python/src/shared'.
print(f"Copiando código de: {SHARED_CODE_SOURCE}") # Adicionado para debug
shared_code_dest = os.path.join(shared_code_intermediate_dir, os.path.basename(SHARED_CODE_SOURCE))
shutil.copytree(SHARED_CODE_SOURCE, shared_code_dest)

# Se o arquivo de dependências existir, instala todas as bibliotecas.
# O arquivo de requirements também precisa ser lido a partir da raiz.
requirements_path = os.path.join(PROJECT_ROOT, REQUIREMENTS_FILE)
if os.path.exists(requirements_path):
# Instala os pacotes diretamente na pasta 'build/python'.
# Isso permite que a Lambda importe as bibliotecas de forma padrão (ex: import requests).
subprocess.check_call(
["pip", "install", "-r", requirements_path, "-t", PYTHON_TOP_LEVEL_DIR, "--no-cache-dir"]
)
else:
# Apenas um aviso caso o arquivo não seja encontrado.
print(f"Aviso: Arquivo '{requirements_path}' não encontrado. Nenhuma dependência externa será instalada.")


# Ponto de entrada do script.
if __name__ == '__main__':
adjust_layer_directory(shared_dir_name="shared", destination="lambda_layer_out_temp")
adjust_layer_directory()
44 changes: 21 additions & 23 deletions iac/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
import os

import aws_cdk as cdk
from adjust_layer_directory import adjust_layer_directory

from iac.template_stack import TemplateStack


from adjust_layer_directory import adjust_layer_directory
from stack.iac_stack import IacStack

print("Starting the CDK")

print("Adjusting the layer directory")
adjust_layer_directory(shared_dir_name="shared", destination="lambda_layer_out_temp")
adjust_layer_directory()
print("Finished adjusting the layer directory")


Expand All @@ -21,26 +19,26 @@
aws_account_id = os.environ.get("AWS_ACCOUNT_ID")
stack_name = os.environ.get("STACK_NAME")

if 'prod' in stack_name:
stage = 'PROD'

elif 'homolog' in stack_name:
stage = 'HOMOLOG'

elif 'dev' in stack_name:
stage = 'DEV'

else:
stage = 'TEST'
stage = os.environ.get("GITHUB_REF_NAME").capitalize()
stack_name = os.environ.get("STACK_NAME")

tags = {
'project': 'Template',
'project': 'PortfolioApi',
'stage': stage,
'stack': 'BACK',
'owner': 'DevCommunity'
'stack': stack_name,
'owner': 'DevCommunity',
}

TemplateStack(app, stack_name=stack_name, env=cdk.Environment(account=aws_account_id, region=aws_region), tags=tags)


app.synth()
IacStack(
app,
stack_id=stack_name,
stack_name=stack_name,
stage=stage,
env=cdk.Environment(
account=aws_account_id,
region=aws_region
),
tags=tags
)

app.synth()
2 changes: 1 addition & 1 deletion iac/cdk.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"app": "python app.py",
"app": "python -m app",
"watch": {
"include": [
"**"
Expand Down
60 changes: 60 additions & 0 deletions iac/components/apigw_construct.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from aws_cdk import aws_apigateway as apigateway
from constructs import Construct
from aws_cdk.aws_apigateway import RestApi, Cors, CorsOptions

class ApigwConstruct(Construct):
rest_api: RestApi

def __init__(
self,
scope: Construct,
construct_id: str,
stage: str,
**kwargs
):

super().__init__(scope, construct_id, **kwargs)

self.stage = stage

cors_options = CorsOptions(
allow_origins=Cors.ALL_ORIGINS,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "QUERY"],
allow_headers=Cors.DEFAULT_HEADERS
)

self.rest_api = RestApi(
self,
id=f"Portfolio_RestApi_{self.stage}",
rest_api_name=f"Portfolio_RestApi_{self.stage}",
description=f"This is the Portfolio RestApi for {self.stage}",
deploy_options=apigateway.StageOptions(
stage_name=stage.lower(),
logging_level=apigateway.MethodLoggingLevel.OFF,
data_trace_enabled=False,
metrics_enabled=True,
),
default_cors_preflight_options=cors_options,
)

# implementação de uma key para mínima proteção de rotas abertas sensíveis
# não precisa ser necessariamente usado

api_key = self.rest_api.add_api_key(
id="AdminApiKey",
api_key_name="portfolio-apigw-admin-key"
)

plan = self.rest_api.add_usage_plan("UsagePlan",
name="AdminPlan",
api_stages=[apigateway.UsagePlanPerApiStage(
api=self.rest_api,
stage=self.rest_api.deployment_stage,
)]
)
plan.add_api_key(api_key)

self.api_gateway_resource = self.rest_api.root.add_resource(
path_part="portfolio",
default_cors_preflight_options=cors_options
)
50 changes: 50 additions & 0 deletions iac/components/dynamo_construct.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from aws_cdk import (
RemovalPolicy,
aws_dynamodb as dynamodb,
)
from constructs import Construct

# Manter alinhado com src.shared.infra.external.dynamo/..._naming/..._TABLE_PREFIX
_PORTFOLIO_TABLE_PREFIX = "PortfolioTable"

RETAINED_STAGES = {"prod", "homolog"}


class DynamoConstruct(Construct):

portfolio_table: dynamodb.Table

def __init__(
self,
scope: Construct,
construct_id: str,
stack_name: str,
stage: str,
**kwargs,
) -> None:
super().__init__(scope, construct_id, **kwargs)

stage_lower = stage.lower()

removal_policy = (
RemovalPolicy.RETAIN if stage_lower in RETAINED_STAGES else RemovalPolicy.DESTROY
)

self.portfolio_table = dynamodb.Table(
self,
id="PortfolioTable",
partition_key=dynamodb.Attribute(
name="pk",
type=dynamodb.AttributeType.STRING,
),
sort_key=dynamodb.Attribute(
name="sk",
type=dynamodb.AttributeType.STRING,
),
billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
removal_policy=removal_policy,
table_name=f"{_PORTFOLIO_TABLE_PREFIX}-{stage_lower}",
point_in_time_recovery_specification=dynamodb.PointInTimeRecoverySpecification(
point_in_time_recovery_enabled=(stage_lower == "prod")
),
)
Loading
Loading