Skip to content

Commit

Permalink
Add PUT requests
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszlacinski committed Oct 27, 2024
1 parent 3aa1421 commit 14959c7
Show file tree
Hide file tree
Showing 15 changed files with 1,038 additions and 93 deletions.
26 changes: 26 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
FROM public.ecr.aws/sam/build-python3.10:latest-x86_64

ENV PATH="/root/.local/bin:/sbin:/usr/sbin:${PATH}"

RUN curl -sSL https://install.python-poetry.org | python3 -

RUN rpm --import https://packages.confluent.io/rpm/7.0/archive.key && \
echo '[Confluent-Clients]' > /etc/yum.repos.d/confluent.repo && \
echo 'name=Confluent Clients repository' >> /etc/yum.repos.d/confluent.repo && \
echo 'baseurl=https://packages.confluent.io/clients/rpm/centos/7/x86_64' >> /etc/yum.repos.d/confluent.repo && \
echo 'gpgcheck=1' >> /etc/yum.repos.d/confluent.repo && \
echo 'gpgkey=https://packages.confluent.io/clients/rpm/archive.key' >> /etc/yum.repos.d/confluent.repo && \
echo 'enabled=1' >> /etc/yum.repos.d/confluent.repo && \
rpm --import https://packages.confluent.io/rpm/7.0/archive.key && \
yum install -y librdkafka-devel && \
pip install --upgrade pip

WORKDIR /var/task
COPY . .

RUN poetry config virtualenvs.create false && \
poetry install --only main --no-interaction --no-ansi && \
cd /var/lang/lib/python3.10/site-packages && \
zip -r9 /var/task/lambda.zip . && \
cd /var/task/src && \
zip -r9 /var/task/lambda.zip .
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,26 @@ API:
/
/{proxy+}
POST
PUT
DELETE
```
with stages:
- dev
- stage
- prod

and stage variable `lambdaAlias` set for each stage to `dev`, `stage`, `prod`, respectively.

### Amazon Lambda

The two functions share the same deployment zip file (`lambda.zip`):
- `authorizer`
- Runtime: Python 3.10
- Handler: `ingest.authorizer`
- Handler: `authorizer.authorizer`
- `api`
- Runtime: Python 3.10
- Handler: `ingest.api`
- Handler: `api.api`
Different versions of the `api` Lambda function have assigned aliases `dev`, `stage`, `prod`.
API Gateway reads the `lambdaAlias` variable uses its value as an alias, `api:${stageVariables.lambdaAlias}` to call a corresponding version of the `api` Lambda function.

Build the deployment zip file:
```
Expand All @@ -41,5 +45,5 @@ Build the deployment zip file:

Update Lambda function code:
```
./scripts/deploy.sh {authorizer|api} {dev|prod}
./scripts/deploy.sh {update_code|publish_version} {api|authorizer} [dev]
```
926 changes: 926 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[tool.poetry]
name = "stac-transaction-api"
version = "0.1.0"
description = "West ESGF STAC Transaction API"
authors = ["Lukasz Lacinski <[email protected]>"]
readme = "README.md"
packages = [
{ include = "src" }
]

[tool.poetry.dependencies]
python = "^3.10"
certifi = "2024.6.2"
cffi = "1.16.0"
charset-normalizer = "3.3.2"
cryptography = "42.0.8"
globus-sdk = "3.41.0"
idna = "3.7"
pyjwt = "2.8.0"
requests = "2.32.3"
fastapi = "0.114.0"
mangum = "0.17.0"
stac-fastapi-types = "3.0.1"
stac-fastapi-extensions = "3.0.1"
stac-fastapi-api = "3.0.1"
confluent-kafka = "2.3.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
14 changes: 0 additions & 14 deletions requirements.txt

This file was deleted.

16 changes: 0 additions & 16 deletions scripts/Dockerfile

This file was deleted.

24 changes: 4 additions & 20 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
#!/bin/bash

SCRIPT_DIR=$(dirname "$(realpath "$0")")
cd "$SCRIPT_DIR" || exit

IMAGE=stac_transaction_api

SRC_DIR=src
BUILD_DIR=build
ZIP_FILE=lambda.zip

docker build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) -t $IMAGE .

cd ..
if [ -e $BUILD_DIR ]; then
rm -r $BUILD_DIR
fi
mkdir $BUILD_DIR
cp requirements.txt $BUILD_DIR
cp $SRC_DIR/*.py $BUILD_DIR

docker run -it --rm -v $(pwd)/$BUILD_DIR:/var/task $IMAGE /bin/bash -c "
cd /var/task && \
pip install -r requirements.txt -t . && \
zip -r9 $ZIP_FILE ."
docker build -t $IMAGE .
docker create --name temp_container $IMAGE
docker cp temp_container:/var/task/$ZIP_FILE .
docker rm temp_container
6 changes: 0 additions & 6 deletions scripts/confluent.repo

This file was deleted.

29 changes: 16 additions & 13 deletions scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ function update_lambda_function {
lambda_function=$1
aws --profile ${AWS_PROFILE} lambda update-function-code \
--function-name ${lambda_function} \
--zip-file fileb://build/${ZIP_FILE}
--zip-file fileb://${ZIP_FILE} \
--no-cli-pager
}

function publish_version {
lambda_function=$1
aws --profile ${AWS_PROFILE} lambda publish-version \
--function-name ${lambda_function} \
--no-cli-pager
}

function update_configuration {
Expand All @@ -17,26 +25,21 @@ function update_configuration {
ENVIRONMENT=${ENVIRONMENT}}"
}

if [ $# -ne 2 ]; then
if [ $# -lt 2 ]; then
echo 'Usage:'
echo ' deploy.sh {authorizer|api {prod|dev}'
echo ' deploy.sh {upodate_code|publish_version} {api|authorizer} [dev]'
exit 1
fi

case $1 in
api)
lambda_function='api'
update_code)
lambda_function=$2
./scripts/build.sh
update_lambda_function ${lambda_function}
#sleep 5
#update_configuration ${lambda_function}
;;
authorizer)
lambda_function='authorizer'
./scripts/build.sh
update_lambda_function ${lambda_function}
#sleep 5
#update_configuration ${lambda_function}
publish_version)
lambda_function=$2
publish_version ${lambda_function}
;;
*)
echo 'Unrecognized lambda function'
Expand Down
2 changes: 1 addition & 1 deletion src/ingest.py → src/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from client import TransactionClient
from producer import KafkaProducer
from utils import load_access_control_policy
from api_settings import event_stream, stac_api
from settings import event_stream, stac_api


app = FastAPI(debug=True)
Expand Down
15 changes: 8 additions & 7 deletions src/authorizer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from globus_sdk import ConfidentialAppAuthClient, AccessTokenAuthorizer, GroupsClient
from globus_sdk.scopes import GroupsScopes
import api_settings as settings
import settings


confidential_client = ConfidentialAppAuthClient(
Expand Down Expand Up @@ -30,26 +30,27 @@ def __call__(self, event, context):
access_token = authorization_header[7:]
response = confidential_client.oauth2_token_introspect(access_token, include="identity_set_detail")
token_info = response.data
resource_arn = event["methodArn"].split("/", 1)[0] + "/*"

# Verify the access token
if not token_info.get("active", False):
return self.generate_policy("unknown", "Deny", event["methodArn"], token_info=token_info)
return self.generate_policy("unknown", "Deny", resource_arn, token_info=token_info)

if settings.api.get("client_id") not in token_info.get("aud", []):
return self.generate_policy(token_info.get("sub"), "Deny", event["methodArn"], token_info=token_info)
return self.generate_policy(token_info.get("sub"), "Deny", resource_arn, token_info=token_info)

if settings.api.get("scope_string") != token_info.get("scope", ""):
return self.generate_policy(token_info.get("sub"), "Deny", event["methodArn"], token_info=token_info)
return self.generate_policy(token_info.get("sub"), "Deny", resource_arn, token_info=token_info)

if settings.api.get("issuer") != token_info.get("iss", ""):
return self.generate_policy(token_info.get("sub"), "Deny", event["methodArn"], token_info=token_info)
return self.generate_policy(token_info.get("sub"), "Deny", resource_arn, token_info=token_info)

# Get the user's groups
groups = self.get_groups(access_token)
if not groups:
return self.generate_policy(token_info.get("sub"), "Deny", event["methodArn"], token_info=token_info)
return self.generate_policy(token_info.get("sub"), "Deny", resource_arn, token_info=token_info)

return self.generate_policy(token_info.get("sub"), "Allow", event["methodArn"], token_info=token_info, groups=groups)
return self.generate_policy(token_info.get("sub"), "Allow", resource_arn, token_info=token_info, groups=groups)

def get_groups(self, token):
"""
Expand Down
24 changes: 15 additions & 9 deletions src/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,16 @@ def authorize(self, item: Item, event: dict, collection_id: str) -> dict:
if getattr(properties, "project", None) != collection_id:
raise ValueError("Item project must match path collection_id")
allowed_groups = self.allowed_groups(properties, self.acl)
print(json.dumps(allowed_groups))
print("allowed groups", json.dumps(allowed_groups))
allowed_groups_uuid = [g.get("uuid") for g in allowed_groups]
print(json.dumps(allowed_groups_uuid))
allowed_groups_uuid.append("8a290d6e-8262-11ef-9fa6-6f9995a83a2e")
print("allowed groups uuid", json.dumps(allowed_groups_uuid))

authorizer = event.get("requestContext").get("authorizer")
access_token_json = authorizer.get("access_token")
user_groups_json = authorizer.get("groups")
print(access_token_json)
print(user_groups_json)
print("access token json", access_token_json)
print("user groups json", user_groups_json)
token_info = json.loads(access_token_json)
user_groups = json.loads(user_groups_json)

Expand Down Expand Up @@ -177,14 +178,19 @@ async def update_item(
},
}

self.producer.produce(
None,
json.dumps(message, default=str).encode("utf-8"),
)
try:
self.producer.produce(
topic="esgf2",
key=item.id.encode("utf-8"),
value=json.dumps(message, default=str).encode("utf-8"),
)
except Exception as e:
print(f"Error producing message: {e}")
raise HTTPException(status_code=500, detail=str(e))

return Response(
content="Item queued for update",
status_code=status.HTTP_202_ACCEPTED,
content="Item queued for update",
)

async def delete_item(
Expand Down
2 changes: 1 addition & 1 deletion src/producer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def produce(self, topic, message):

class StdoutProducer(BaseProducer):
def produce(self, topic, message):
print(message)
print("message", message)


class KafkaProducer(BaseProducer):
Expand Down
2 changes: 1 addition & 1 deletion src/api_settings.py → src/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@
"topic": "esgf2",
}

if os.environ.get("PRODUCER_DEBUG"):
if os.environ.get("PRODUCER_DEBUG").lower() == "true":
event_stream["config"]["debug"] = "all"
event_stream["config"]["log_level"] = 7
2 changes: 1 addition & 1 deletion src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

def get_secret(region_name, secret_name):
client = boto3.client("secretsmanager", region_name=region_name)
print(secret_name)
print("secret_name", secret_name)
try:
response = client.get_secret_value(SecretId=secret_name)
if "SecretString" in response:
Expand Down

0 comments on commit 14959c7

Please sign in to comment.