diff --git a/.gitignore b/.gitignore index ad61461..31a0bcc 100644 --- a/.gitignore +++ b/.gitignore @@ -165,3 +165,5 @@ build .secrets shard.lock + +template diff --git a/functions.yml b/functions.yml index 67d34de..cb9324b 100644 --- a/functions.yml +++ b/functions.yml @@ -27,4 +27,12 @@ functions: image: ralsina/c-busqueda:latest build_args: ADDITIONAL_PACKAGE: gnuplot + iol: + lang: python3-fastapi + handler: ./iol + image: ralsina/iol:latest + secrets: + - iol-pass + - iol-user + - iol-api-secret diff --git a/iol/__init__.py b/iol/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/iol/handler.py b/iol/handler.py new file mode 100644 index 0000000..77aa0f9 --- /dev/null +++ b/iol/handler.py @@ -0,0 +1,64 @@ +import logging +import time +from functools import lru_cache + +import requests as r +from fastapi import APIRouter + +BASE_URL = "https://api.invertironline.com/" +USER = open("/var/openfaas/secrets/iol-user").read().strip() +PASS = open("/var/openfaas/secrets/iol-pass").read().strip() +SECRET = open("/var/openfaas/secrets/iol-api-secret").read().strip() + +router = APIRouter() + +def get_ttl_hash(seconds=3600): + """Return the same value withing `seconds` time period""" + return round(time.time() / seconds) + +@lru_cache +def get_token(ttl_hash=None): + logging.error("getting token") + url = BASE_URL + "token" + data = {"username": USER, "password": PASS, "grant_type": "password"} + response = r.post(url, data=data) + access_token = response.json()["access_token"] + refresh_token = response.json()["refresh_token"] + return access_token, refresh_token + + +def refresh_token(refresh_token): + url = BASE_URL + "token" + data = {"refresh_token": refresh_token, "grant_type": "refresh_token"} + response = r.post(url, data=data) + access_token = response.json()["access_token"] + refresh_token = response.json()["refresh_token"] + return access_token, refresh_token + + +paises = { + "US": "estados_Unidos", + "AR": "argentina", +} + +@lru_cache +def get(url, ttl_hash=None): + logging.error("getting data") + access, refresh = get_token(ttl_hash=get_ttl_hash()) + response = r.get(url, headers={"Authorization": "Bearer " + access, "Accept": "application/json"}) + return response.json() + + +@router.get("/{secret}/accion/{pais}/{accion}") +def get_accion(secret, pais, accion): + if secret != SECRET: + raise Exception("BAD") + pais = pais.upper() + accion = accion.upper() + access, refresh = get_token(ttl_hash=get_ttl_hash()) + url = ( + BASE_URL + + f"/api/v2/Cotizaciones/acciones/{pais}/Todos?cotizacionInstrumentoModel.instrumento=acciones&cotizacionInstrumentoModel.pais={paises[pais]}&api_key={access}" + ) + data = get(url, ttl_hash=get_ttl_hash()) + return [a for a in data["titulos"] if a["simbolo"] == accion][0] diff --git a/iol/requirements.txt b/iol/requirements.txt new file mode 100644 index 0000000..7b68ec4 --- /dev/null +++ b/iol/requirements.txt @@ -0,0 +1,3 @@ +fastapi +requests +uvicorn[standard] diff --git a/template/python3-fastapi/Dockerfile b/template/python3-fastapi/Dockerfile new file mode 100644 index 0000000..254b89d --- /dev/null +++ b/template/python3-fastapi/Dockerfile @@ -0,0 +1,49 @@ +FROM --platform=${TARGETPLATFORM:-linux/amd64} ghcr.io/openfaas/of-watchdog:0.9.10 as watchdog +FROM --platform=${TARGETPLATFORM:-linux/amd64} python:3.11-alpine + +COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog +RUN chmod +x /usr/bin/fwatchdog + +ARG ADDITIONAL_PACKAGE +RUN apk --no-cache add musl-dev gcc make ${ADDITIONAL_PACKAGE} + +# Add non root user +RUN addgroup -S app && adduser app -S -G app +RUN chown app /home/app + +USER app + +ENV PATH=$PATH:/home/app/.local/bin + +WORKDIR /home/app/ + +COPY requirements.txt . +USER root +RUN pip install -r requirements.txt +USER app +COPY index.py . + +RUN mkdir -p function +RUN touch ./function/__init__.py +WORKDIR /home/app/function/ +COPY function/requirements.txt . +RUN pip install --user -r requirements.txt + +WORKDIR /home/app/ + +USER root +# remove build dependencies +RUN apk del musl-dev gcc make +COPY function function +RUN chown -R app:app ./ +USER app + +ENV fprocess="uvicorn index:app --workers 1 --host 0.0.0.0 --port 8000" + +ENV cgi_headers="true" +ENV mode="http" +ENV upstream_url="http://127.0.0.1:8000" + +HEALTHCHECK --interval=5s CMD [ -e /tmp/.lock ] || exit 1 + +CMD ["fwatchdog"] diff --git a/template/python3-fastapi/function/__init__.py b/template/python3-fastapi/function/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/template/python3-fastapi/function/handler.py b/template/python3-fastapi/function/handler.py new file mode 100644 index 0000000..2715f0d --- /dev/null +++ b/template/python3-fastapi/function/handler.py @@ -0,0 +1,51 @@ +# author: Justin Guese, 11.3.22, justin@datafortress.cloud +from fastapi import HTTPException +from fastapi import APIRouter +from pydantic import BaseModel +from typing import Dict, List +from os import environ +import glob + +# reads in secrets to environment variables, such that they can be +# easily used with environ["SECRET_NAME"] +def readSecretToEnv(secretpath): + secretname = secretpath.split('/')[-1] + with open(secretpath, "r") as f: + environ[secretname] = f.read() +for secret in glob.glob("/var/openfaas/secrets/*"): + readSecretToEnv(secret) + +router = APIRouter() + +# just as an example +class User(BaseModel): + id: int + name: str + age: int + colleagues: List[str] + +class ResponseModel(BaseModel): + data: Dict + # user: User + # otherStuff: str + +@router.post("/", response_model = ResponseModel, tags=["Main Routes"]) +def handle(request: Dict): + """handle a request to the function + Args: + req (dict): request body + """ + try: + res = ResponseModel(data=request) + except Exception as e: + raise HTTPException(status_code=500, detail=str(repr(e))) + return res + +@router.get("/", response_model = ResponseModel, tags=["Main Routes"]) +def get(): + return ResponseModel(data={"message": "Hello from OpenFAAS!"}) + +# again just as an example, delete this if not required +@router.get("/users/{user_id}", response_model = User, tags=["Main Routes"]) +def getUser(user_id: int): + return User(id = user_id, name="Exampleuser", age=20, colleagues=["Colleague 1", "Colleague 2"]) \ No newline at end of file diff --git a/template/python3-fastapi/function/requirements.txt b/template/python3-fastapi/function/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/template/python3-fastapi/index.py b/template/python3-fastapi/index.py new file mode 100644 index 0000000..e4348ea --- /dev/null +++ b/template/python3-fastapi/index.py @@ -0,0 +1,19 @@ +# author: Justin Guese, 11.3.22, justin@datafortress.cloud +from os import environ +import glob +from fastapi import FastAPI, Request +from fastapi.openapi.docs import get_swagger_ui_html +from function.handler import router + +app = FastAPI() +app.include_router(router) + +# required to render /docs path +@app.get("/docs", include_in_schema=False) +async def custom_swagger_ui_html(req: Request): + root_path = req.scope.get("root_path", "").rstrip("/") + openapi_url = root_path + app.openapi_url + return get_swagger_ui_html( + openapi_url=openapi_url, + title="API", + ) \ No newline at end of file diff --git a/template/python3-fastapi/requirements.txt b/template/python3-fastapi/requirements.txt new file mode 100644 index 0000000..9ba9ac6 --- /dev/null +++ b/template/python3-fastapi/requirements.txt @@ -0,0 +1,2 @@ +fastapi +uvicorn \ No newline at end of file diff --git a/template/python3-fastapi/template.yml b/template/python3-fastapi/template.yml new file mode 100644 index 0000000..a0a5557 --- /dev/null +++ b/template/python3-fastapi/template.yml @@ -0,0 +1,2 @@ +language: python3-fastapi +fprocess: uvicorn index:app --workers 1 --host 0.0.0.0 --port 8000