generated from pagopa/template-aws-infrastructure
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: lambda update idp status (#605)
* feat: add oneid-lambda-update-idp-status * fix: typo * feat: add github action to deploy lambda-update-idp-status * chore: rename PK from 'idp' to 'entityID' * Update .github/workflows/deploy-lambda-update-idp-status.yml Co-authored-by: Giuseppe Gangemi <[email protected]> --------- Co-authored-by: Giuseppe Gangemi <[email protected]>
- Loading branch information
1 parent
85382ef
commit b233129
Showing
2 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
name: Deploy Lambda update idp status | ||
|
||
on: | ||
push: | ||
branches: | ||
- "main" | ||
paths: | ||
- "**/src/oneid/oneid-lambda-update-idp-status/**" | ||
workflow_dispatch: | ||
inputs: | ||
environment: | ||
description: 'Choose environment' | ||
type: choice | ||
required: true | ||
default: dev | ||
options: | ||
- dev | ||
- uat | ||
- prod | ||
jobs: | ||
setup: | ||
runs-on: ubuntu-22.04 | ||
outputs: | ||
matrix: ${{ steps.setmatrix.outputs.matrix }} | ||
|
||
steps: | ||
- name: Set Dynamic Env Matrix | ||
id: setmatrix | ||
run: | | ||
echo "github.ref ${{ github.ref }}" | ||
echo "event name ${{ github.event_name }}" | ||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | ||
if [ "${{ github.event.inputs.environment }}" == "prod" ]; then | ||
matrixStringifiedObject="{\"include\":[{\"environment\":\"prod\", \"region\":\"eu-south-1\"}, {\"environment\":\"prod\", \"region\":\"eu-central-1\"}]}" | ||
else | ||
matrixStringifiedObject="{\"include\":[{\"environment\":\"${{ github.event.inputs.environment }}\", \"region\":\"eu-south-1\"}]}" | ||
fi | ||
else | ||
matrixStringifiedObject="{\"include\":[{\"environment\":\"dev\", \"region\":\"eu-south-1\"}, {\"environment\":\"uat\", \"region\":\"eu-south-1\"}, {\"environment\":\"prod\", \"region\":\"eu-south-1\"}, {\"environment\":\"prod\", \"region\":\"eu-central-1\"}]}" | ||
fi | ||
echo "matrix=$matrixStringifiedObject" >> $GITHUB_OUTPUT | ||
build: | ||
runs-on: ubuntu-22.04 | ||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | ||
|
||
- name: Zip Lambda | ||
working-directory: src/oneid/oneid-lambda-update-idp-status | ||
run: | | ||
mkdir -p ./target && zip -r target/oneid-lambda-update-idp-status.zip . -x "*.dist-info/*" -x "target/*" | ||
- name: Archive build artifacts | ||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 | ||
with: | ||
name: update-idp-status-lambda | ||
path: ./src/oneid/oneid-lambda-update-idp-status/target/oneid-lambda-update-idp-status.zip | ||
|
||
deploy: | ||
name: Deploy lambda update idp status ${{ matrix.environment }}-${{ matrix.region }} | ||
if: ${{ needs.setup.outputs.matrix != '' }} | ||
runs-on: ubuntu-22.04 | ||
needs: [ setup, build ] | ||
strategy: | ||
matrix: ${{ fromJson(needs.setup.outputs.matrix) }} | ||
|
||
continue-on-error: false | ||
environment: ${{ matrix.environment == 'prod' && format('{0}/{1}', matrix.environment, matrix.region) || matrix.environment }} | ||
env: | ||
ENV_SHORT: ${{ fromJSON('{"dev":"d","uat":"u","prod":"p"}')[matrix.environment] }} | ||
REGION_SHORT: ${{ fromJSON('{"eu-south-1":"es-1","eu-central-1":"ec-1"}')[matrix.region] }} | ||
permissions: | ||
id-token: write | ||
contents: read | ||
|
||
steps: | ||
- name: Download build artifacts | ||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e | ||
with: | ||
name: update-idp-status-lambda | ||
path: ./src/oneid/oneid-lambda-update-idp-status/target | ||
|
||
- name: Configure AWS Credentials | ||
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 | ||
with: | ||
role-to-assume: ${{ vars.IAM_ROLE_DEPLOY_LAMBDA }} | ||
aws-region: ${{ matrix.region }} | ||
|
||
- name: Update Lambda function (${{ matrix.environment }}) | ||
run: | | ||
aws s3 cp src/oneid/oneid-lambda-update-idp-status/target/oneid-lambda-update-idp-status.zip s3://${{vars.LAMBDA_CODE_BUCKET_NAME}}/${{vars.LAMBDA_UPDATE_IDP_STATUS_KEY}} | ||
- name: Deploy Lambda function (${{ matrix.environment }}) | ||
run: | | ||
aws lambda update-function-code \ | ||
--function-name oneid-${{ env.REGION_SHORT }}-${{ env.ENV_SHORT }}-update-idp-status \ | ||
--s3-bucket ${{vars.LAMBDA_CODE_BUCKET_NAME}} --s3-key ${{vars.LAMBDA_UPDATE_IDP_STATUS_KEY}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
""" | ||
Lambda update idp status | ||
""" | ||
|
||
import json | ||
import logging | ||
import os | ||
import time | ||
|
||
import boto3 | ||
|
||
AWS_REGION = os.getenv("AWS_REGION") | ||
IDP_STATUS_DYNAMODB_TABLE = os.getenv("IDP_STATUS_DYNAMODB_TABLE") | ||
ASSETS_S3_BUCKET = os.getenv("ASSETS_S3_BUCKET") | ||
IDP_STATUS_S3_FILE_NAME = os.getenv("IDP_STATUS_S3_FILE_NAME") | ||
LATEST_POINTER = "latest" | ||
IDP_ERROR_ALARM = "IDPErrorAlarm" | ||
IDP_SUCCESS_ALARM = "IDPSuccessAlarm" | ||
IDP_STATUS_OK = "OK" | ||
IDP_STATUS_KO = "KO" | ||
|
||
# Initialize a logger | ||
logger = logging.getLogger() | ||
|
||
# Initialize a boto3 client for CloudWatch | ||
cloudwatch_client = boto3.client("cloudwatch", region_name=AWS_REGION) | ||
|
||
# Initialize a boto3 client for DynamoDB | ||
dynamodb_client = boto3.client("dynamodb", region_name=AWS_REGION) | ||
|
||
# Initialize a boto3 client for S3 | ||
s3_client = boto3.client("s3", region_name=AWS_REGION) | ||
|
||
|
||
def get_event_data(event): | ||
""" | ||
Extract the event data from event | ||
""" | ||
# Extract the event data considering that it is a CloudWatch alarm event | ||
# The alarm name is in the format {ALARM_TYPE}-{IDP} | ||
# ALARM_TYPE is one of the following: IDPErrorAlarm, IDPSuccessAlarm | ||
# whilst IDP is one of the IDP entity ids | ||
alarm_data = event["alarmData"] | ||
alarm_name = alarm_data["alarmName"] | ||
alarm_type, idp = alarm_name.split("-") | ||
|
||
return alarm_type, idp | ||
|
||
|
||
def update_idp_status(idp, status) -> bool: | ||
""" | ||
Update the IDP status in the DynamoDB table | ||
""" | ||
# Update the IDP status in the DynamoDB table | ||
old_item = None | ||
# Get the current Unix timestamp as a string | ||
current_timestamp = str(int(time.time())) | ||
|
||
# Remove the latest IDP status | ||
try: | ||
response = dynamodb_client.delete_item( | ||
TableName=IDP_STATUS_DYNAMODB_TABLE, | ||
Key={"entityID": {"S": idp}, "pointer": {"S": LATEST_POINTER}}, | ||
ReturnValues="ALL_OLD", | ||
) | ||
old_item = response["Attributes"] | ||
logger.info("Deleted item: %s", response) | ||
except Exception as e: | ||
logger.error("Error deleting item: %s", e) | ||
return False | ||
|
||
if not old_item: | ||
logger.error("No item found with PK: %s and RK: %s", idp, LATEST_POINTER) | ||
return False | ||
|
||
old_status = old_item["status"]["S"] | ||
|
||
# Add new entry with unix timestamp as the range key and old status as the status | ||
try: | ||
dynamodb_client.put_item( | ||
TableName=IDP_STATUS_DYNAMODB_TABLE, | ||
Item={ | ||
"entityID": {"S": idp}, | ||
"pointer": {"S": current_timestamp}, | ||
"status": {"S": old_status}, | ||
}, | ||
) | ||
except Exception as e: | ||
logger.error("Error inserting item: %s", e) | ||
return False | ||
|
||
# Add new latest entry with the new status | ||
try: | ||
dynamodb_client.put_item( | ||
TableName=IDP_STATUS_DYNAMODB_TABLE, | ||
Item={ | ||
"entityID": {"S": idp}, | ||
"pointer": {"S": LATEST_POINTER}, | ||
"status": {"S": status}, | ||
}, | ||
) | ||
except Exception as e: | ||
logger.error("Error inserting item: %s", e) | ||
return False | ||
|
||
return True | ||
|
||
|
||
def get_all_latest_status(): | ||
""" | ||
Get all items with {LATEST_POINTER} as the sort key from DynamoDB | ||
""" | ||
try: | ||
response = dynamodb_client.scan( | ||
TableName=IDP_STATUS_DYNAMODB_TABLE, | ||
FilterExpression="pointer = :pointer", | ||
ExpressionAttributeValues={":pointer": {"S": LATEST_POINTER}}, | ||
) | ||
return response.get("Items", []) | ||
except Exception as e: | ||
logger.error("Error scanning items: %s", e) | ||
return [] | ||
|
||
|
||
def update_s3_asset_file(idp_latest_status) -> bool: | ||
""" | ||
Update the S3 asset file with the latest IDP status | ||
""" | ||
idp_status_list = [ | ||
{"IDP": idp["entityID"]["S"], "Status": idp["status"]["S"]} | ||
for idp in idp_latest_status | ||
] | ||
|
||
# Convert the list to JSON | ||
idp_status_json = json.dumps(idp_status_list) | ||
|
||
# Upload the JSON file to S3 | ||
try: | ||
s3_client.put_object( | ||
Bucket=ASSETS_S3_BUCKET, | ||
Key=IDP_STATUS_S3_FILE_NAME, | ||
Body=idp_status_json, | ||
ContentType="application/json", | ||
) | ||
logger.info( | ||
"Uploaded IDP status to S3: %s/%s", | ||
ASSETS_S3_BUCKET, | ||
IDP_STATUS_S3_FILE_NAME, | ||
) | ||
except Exception as e: | ||
logger.error("Error uploading IDP status to S3: %s", e) | ||
return False | ||
|
||
return True | ||
|
||
|
||
def lambda_handler(event, context): | ||
""" | ||
Lambda handler | ||
""" | ||
logger.info("Received event: %s", json.dumps(event)) | ||
# Extract the event type and the idp | ||
alarm_type, idp = get_event_data(event) | ||
# Related success alarm for the idp | ||
alarm_success = f"{IDP_SUCCESS_ALARM}-{idp}" | ||
|
||
if alarm_type == IDP_ERROR_ALARM: | ||
# Update the IDP status to {IDP_STATUS_KO} | ||
if update_idp_status(idp, IDP_STATUS_KO): | ||
# Enable cloudwatch success alarm for the IDP | ||
cloudwatch_client.enable_alarm_actions( | ||
AlarmNames=[ | ||
alarm_success, | ||
] | ||
) | ||
logger.info("Enabled alarm actions for %s", alarm_success) | ||
else: | ||
logger.error("Error updating IDP status") | ||
return {"statusCode": 500, "body": json.dumps("Error updating IDP status")} | ||
elif alarm_type == IDP_SUCCESS_ALARM: | ||
# Update the IDP status to {IDP_STATUS_OK} | ||
if update_idp_status(idp, IDP_STATUS_OK): | ||
# Disable cloudwatch alarm for the IDP | ||
cloudwatch_client.disable_alarm_actions( | ||
AlarmNames=[ | ||
alarm_success, | ||
] | ||
) | ||
logger.info("Disabled alarm actions for %s", alarm_success) | ||
else: | ||
logger.error("Error updating IDP status") | ||
return {"statusCode": 500, "body": json.dumps("Error updating IDP status")} | ||
else: | ||
logger.error("Invalid alarm type: %s", alarm_type) | ||
return {"statusCode": 400, "body": json.dumps("Invalid alarm type")} | ||
|
||
# Update the S3 asset file with latest IDP status | ||
|
||
# Get the latest IDP status from DynamoDB | ||
idp_latest_status = get_all_latest_status() | ||
|
||
if update_s3_asset_file(idp_latest_status): | ||
return { | ||
"statusCode": 200, | ||
"body": json.dumps("IDP status updated successfully"), | ||
} | ||
|
||
return {"statusCode": 500, "body": json.dumps("Error updating S3 asset file")} |