Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(openchallenges): add new openchallenges-data-lambda project #2954

Merged
merged 4 commits into from
Dec 20, 2024
Merged
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
244 changes: 244 additions & 0 deletions apps/openchallenges/data-lambda/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@

# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode

### Linux ###
*~

# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

# KDE directory preferences
.directory

# Linux trash folder which might appear on any partition or disk
.Trash-*

# .nfs files are created when an open file is removed but is still being accessed
.nfs*

### OSX ###
*.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### PyCharm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries

# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml

# Gradle:
.idea/**/gradle.xml
.idea/**/libraries

# CMake
cmake-build-debug/

# Mongo Explorer plugin:
.idea/**/mongoSettings.xml

## File-based project format:
*.iws

## Plugin-specific files:

# IntelliJ
/out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Cursive Clojure plugin
.idea/replstate.xml

# Ruby plugin and RubyMine
/.rakeTasks

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

### PyCharm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721

# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr

# Sonarlint plugin
.idea/sonarlint

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
.pytest_cache/
nosetests.xml
coverage.xml
*.cover
.hypothesis/

# Translations
*.mo
*.pot

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
# .python-version

# celery beat schedule file
celerybeat-schedule.*

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history

### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db

# Folder config file
Desktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msm
*.msp

# Windows shortcuts
*.lnk

# Build folder

*/build/*

# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
1 change: 1 addition & 0 deletions apps/openchallenges/data-lambda/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13.0
18 changes: 18 additions & 0 deletions apps/openchallenges/data-lambda/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM python:3.13.0-slim-bullseye AS builder

# Install the same version of Poetry as inside the dev container
RUN pip install --no-cache-dir poetry==1.8.3

WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN poetry export --without-hashes --format=requirements.txt > requirements.txt

FROM public.ecr.aws/lambda/python:3.13

COPY --from=builder /app/requirements.txt ${LAMBDA_TASK_ROOT}/
COPY sandbox_lambda_python/app.py ${LAMBDA_TASK_ROOT}/

RUN python3.13 -m pip install --no-cache-dir -r requirements.txt -t .

# Command can be overwritten by providing a different command in the template directly.
CMD ["app.lambda_handler"]
45 changes: 45 additions & 0 deletions apps/openchallenges/data-lambda/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# OpenChallenges Data Lambda

## Build the project

```console
nx build openchallenges-data-lambda
```

## Build the Docker image of the Lambda function

```console
nx build-image openchallenges-data-lambda
```

## Start the Lambda function locally with Docker Compose

Starts the Lambda function in the foreground, allowing you to view logs and interact with it
directly.

```console
nx serve openchallenges-data-lambda
```

Starts the Lambda function in detached mode, running it in the background. This is useful if you
want to continue using the terminal for other tasks while the function runs.

```console
nx serve-detach openchallenges-data-lambda
```

## Invoke the Lambda function locally

To invoke the Lambda function after starting it locally, use the following command:

```console
nx run openchallenges-data-lambda:invoke --event <path-to-json-file>
```

Replace `<path-to-json-file>` with the path to your JSON file containing the event payload relative
to the location of the project folder. For example, if your event payload is stored in a file
located at `events/event.json` relative to the project folder:

```console
nx run openchallenges-data-lambda:invoke --event events/event.json
```
Empty file.
62 changes: 62 additions & 0 deletions apps/openchallenges/data-lambda/events/event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"body": "{\"message\": \"hello world\"}",
"resource": "/hello",
"path": "/hello",
"httpMethod": "GET",
"isBase64Encoded": false,
"queryStringParameters": {
"foo": "bar"
},
"pathParameters": {
"proxy": "/path/to/resource"
},
"stageVariables": {
"baz": "qux"
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "en-US,en;q=0.8",
"Cache-Control": "max-age=0",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Host": "1234567890.execute-api.us-east-1.amazonaws.com",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Custom User Agent String",
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"requestContext": {
"accountId": "123456789012",
"resourceId": "123456",
"stage": "prod",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"requestTime": "09/Apr/2015:12:34:56 +0000",
"requestTimeEpoch": 1428582896000,
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"accessKey": null,
"sourceIp": "127.0.0.1",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "Custom User Agent String",
"user": null
},
"path": "/prod/hello",
"resourcePath": "/hello",
"httpMethod": "POST",
"apiId": "1234567890",
"protocol": "HTTP/1.1"
}
}
13 changes: 13 additions & 0 deletions apps/openchallenges/data-lambda/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

PYTHON_VERSION=$(cat ".python-version")

pyenv install --skip-existing $PYTHON_VERSION

# Initializing pyenv again solves an issue encountered by GitHub action where the version of Python
# installed above is not detected.
eval "$(pyenv init -)"

pyenv local $PYTHON_VERSION
poetry env use $(pyenv which python)
poetry install
183 changes: 183 additions & 0 deletions apps/openchallenges/data-lambda/poetry.lock

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions apps/openchallenges/data-lambda/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "openchallenges-data-lambda",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"targets": {
"prepare": {
"executor": "nx:run-commands",
"options": {
"command": "./install.sh",
"cwd": "{projectRoot}"
}
},
"build": {
"executor": "nx:run-commands",
"options": {
"command": "sam build",
"cwd": "{projectRoot}"
}
},
"serve": {
"executor": "nx:run-commands",
"options": {
"command": "docker/sandbox/serve.sh {projectName}"
}
},
"serve-detach": {
"executor": "nx:run-commands",
"options": {
"command": "docker/sandbox/serve-detach.sh {projectName}"
}
},
"invoke": {
"executor": "nx:run-commands",
"options": {
"command": "curl -X POST 'http://localhost:9000/2015-03-31/functions/function/invocations' --data @{args.event}",
"cwd": "{projectRoot}"
}
}
},
"tags": ["language:python"]
}
15 changes: 15 additions & 0 deletions apps/openchallenges/data-lambda/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[tool.poetry]
name = "openchallenges-data-lambda"
version = "0.1.0"
description = "A containerized AWS Lambda to import challenge data into the OC database"
authors = ["Verena Chung <verena.chung@sagebase.org>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "3.13.0"
requests = "2.32.3"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
30 changes: 30 additions & 0 deletions apps/openchallenges/data-lambda/samconfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# More information about the configuration file can be found here:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
version = 0.1

[default.global.parameters]
stack_name = "openchallenges-data-lambda"

[default.build.parameters]
parallel = true

[default.validate.parameters]
lint = true

[default.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = true
resolve_image_repos = true

[default.package.parameters]
resolve_s3 = true

[default.sync.parameters]
watch = true

[default.local_start_api.parameters]
warm_containers = "EAGER"

[default.local_start_lambda.parameters]
warm_containers = "EAGER"
Empty file.
33 changes: 33 additions & 0 deletions apps/openchallenges/data-lambda/sandbox_lambda_python/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import json


def lambda_handler(event, context):
"""Sample pure Lambda function
Parameters
----------
event: dict, required
API Gateway Lambda Proxy Input Format
Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
context: object, required
Lambda Context runtime methods and attributes
Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html
Returns
------
API Gateway Lambda Proxy Output Format: dict
Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
"""

return {
"statusCode": 200,
"body": json.dumps(
{
"message": "hello world",
}
),
}
43 changes: 43 additions & 0 deletions apps/openchallenges/data-lambda/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
python3.13
Sample SAM Template for lambda-python
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3

Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
PackageType: Image
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
Metadata:
Dockerfile: Dockerfile
DockerContext: .
DockerTag: python3.13-v1

Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
Description: 'API Gateway endpoint URL for Prod stage for Hello World function'
Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/'
HelloWorldFunction:
Description: 'Hello World Lambda Function ARN'
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: 'Implicit IAM Role created for Hello World function'
Value: !GetAtt HelloWorldFunctionRole.Arn
Empty file.
Empty file.
72 changes: 72 additions & 0 deletions apps/openchallenges/data-lambda/tests/unit/test_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import json

import pytest

from hello_world import app


@pytest.fixture()
def apigw_event():
"""Generates API GW Event"""

return {
"body": '{ "test": "body"}',
"resource": "/{proxy+}",
"requestContext": {
"resourceId": "123456",
"apiId": "1234567890",
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"accountId": "123456789012",
"identity": {
"apiKey": "",
"userArn": "",
"cognitoAuthenticationType": "",
"caller": "",
"userAgent": "Custom User Agent String",
"user": "",
"cognitoIdentityPoolId": "",
"cognitoIdentityId": "",
"cognitoAuthenticationProvider": "",
"sourceIp": "127.0.0.1",
"accountId": "",
},
"stage": "prod",
},
"queryStringParameters": {"foo": "bar"},
"headers": {
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
"Accept-Language": "en-US,en;q=0.8",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Mobile-Viewer": "false",
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
"CloudFront-Viewer-Country": "US",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Upgrade-Insecure-Requests": "1",
"X-Forwarded-Port": "443",
"Host": "1234567890.execute-api.us-east-1.amazonaws.com",
"X-Forwarded-Proto": "https",
"X-Amz-Cf-Id": "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==",
"CloudFront-Is-Tablet-Viewer": "false",
"Cache-Control": "max-age=0",
"User-Agent": "Custom User Agent String",
"CloudFront-Forwarded-Proto": "https",
"Accept-Encoding": "gzip, deflate, sdch",
},
"pathParameters": {"proxy": "/examplepath"},
"httpMethod": "POST",
"stageVariables": {"baz": "qux"},
"path": "/examplepath",
}


def test_lambda_handler(apigw_event, mocker):

ret = app.lambda_handler(apigw_event, "")
data = json.loads(ret["body"])

assert ret["statusCode"] == 200
assert "message" in ret["body"]
assert data["message"] == "hello world"