Skip to content

Commit

Permalink
feat: lambda update idp status (#605)
Browse files Browse the repository at this point in the history
* 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
BenitoVisone and giuseppe-gangemi authored Jan 29, 2025
1 parent 85382ef commit b233129
Show file tree
Hide file tree
Showing 2 changed files with 308 additions and 0 deletions.
100 changes: 100 additions & 0 deletions .github/workflows/deploy-lambda-update-idp-status.yml
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}}
208 changes: 208 additions & 0 deletions src/oneid/oneid-lambda-update-idp-status/lambda.py
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")}

0 comments on commit b233129

Please sign in to comment.