From 0fd1efb7eb7cdb688fc3657e9cbb3c61255d3fc6 Mon Sep 17 00:00:00 2001 From: Allison King Date: Tue, 10 May 2022 16:43:38 -0400 Subject: [PATCH] Serve static files (#621) * Add static build dir to gitignore * Proof of concept serving static files * Add tests for static file * Add a placeholder for copying frontend files to Dockerfile * Add static file server to changelog * Add docstrings * Attempt to fix CI tests * create an index page on startup if it doesn't exist * update the changelog * Consolidate calls to WEBAPP_INDEX * Restore test Co-authored-by: ThomasLaPiana --- .gitignore | 1 + CHANGELOG.md | 6 +++++- Dockerfile | 14 ++++++++++++++ src/fidesapi/main.py | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/core/test_api.py | 7 +++++++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index abd244b45b..d28c7c8572 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ fidesapi/src/main/resources/application.conf docs/fides/docs/api/openapi.json docs/fides/docs/schemas/config_schema.json +fidesapi/build/static ## generic files to ignore *~ diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f552b4383..388223c3f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ The types of changes are: * Added dependabot to keep dependencies updated * Include a warning for any orphan datasets as part of the `apply` command. +### Changed + +* Comparing server and CLI versions ignores `.dirty` only differences, and is quiet on success when running general CLI commands + ### Developer Experience * Replaced `make` with `nox` @@ -37,6 +41,7 @@ The types of changes are: * Resolved a failure with populating applicable data subject rights to a data map * Updated `fideslog` to v1.1.5, resolving an issue where some exceptions thrown by the SDK were not handled as expected +* Host static files via fidesapi [#621](https://github.com/ethyca/fides/pull/621) ## [1.6.0](https://github.com/ethyca/fides/compare/1.5.3...1.6.0) - 2022-05-02 @@ -50,7 +55,6 @@ The types of changes are: * added isort as a CI check * Include `tests/` in all static code checks (e.g. `mypy`, `pylint`) -* Comparing server and CLI versions ignores `.dirty` only differences, and is quiet on success when running general CLI commands ### Changed diff --git a/Dockerfile b/Dockerfile index f899ec2b3a..2c447e0b3f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,14 @@ FROM --platform=linux/amd64 python:3.8-slim-buster as base # Update pip in the base image since we'll use it everywhere RUN pip install -U pip +#################### +## Build frontend ## +#################### +FROM base as frontend +# Placeholder until we have a frontend app scaffolded +RUN echo "

Hello world!

" > /tmp/index.html + + ####################### ## Tool Installation ## ####################### @@ -76,6 +84,9 @@ ENV PYTHONUNBUFFERED=TRUE # Enable detection of running within Docker ENV RUNNING_IN_DOCKER=TRUE +# Make a static files directory +RUN mkdir -p src/fidesapi/build/static + EXPOSE 8080 CMD ["fidesctl", "webserver"] @@ -97,3 +108,6 @@ FROM builder as prod # Install without a symlink RUN python setup.py sdist RUN pip install dist/fidesctl-*.tar.gz + +# Copy frontend build over +COPY --from=frontend /tmp/index.html src/fidesapi/build/static/ diff --git a/src/fidesapi/main.py b/src/fidesapi/main.py index 5afa5a8c5a..ea9c18ad43 100644 --- a/src/fidesapi/main.py +++ b/src/fidesapi/main.py @@ -5,9 +5,12 @@ from datetime import datetime from enum import Enum from logging import WARNING +from pathlib import Path from typing import Callable, Dict from fastapi import FastAPI, Request, Response, status +from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles from loguru import logger as log from uvicorn import Config, Server @@ -18,6 +21,9 @@ from fidesapi.utils.logger import setup as setup_logging from fidesctl.core.config import FidesctlConfig, get_config +WEBAPP_DIRECTORY = Path("src/fidesapi/build/static") +WEBAPP_INDEX = WEBAPP_DIRECTORY / "index.html" + app = FastAPI(title="fidesctl") CONFIG: FidesctlConfig = get_config() @@ -42,6 +48,18 @@ async def configure_db(database_url: str) -> None: await database.init_db(database_url) +@app.on_event("startup") +async def create_webapp_dir_if_not_exists() -> None: + """Creates the webapp directory if it doesn't exist.""" + + if not WEBAPP_INDEX.is_file(): + WEBAPP_DIRECTORY.mkdir(parents=True, exist_ok=True) + with open(WEBAPP_DIRECTORY / "index.html", "w") as index_file: + index_file.write("

Privacy is a Human Right!

") + + app.mount("/static", StaticFiles(directory=WEBAPP_DIRECTORY), name="static") + + @app.on_event("startup") async def setup_server() -> None: "Run all of the required setup steps for the webserver." @@ -114,6 +132,30 @@ async def db_action(action: DBActions) -> Dict: return {"data": {"message": f"Fidesctl database {action_text}"}} +# Configure the static file paths last since otherwise it will take over all paths +@app.get("/") +def read_index() -> Response: + """ + Return an index.html at the root path + """ + return FileResponse(WEBAPP_INDEX) + + +@app.get("/{catchall:path}", response_class=FileResponse) +def read_other_paths(request: Request) -> FileResponse: + """ + Return related frontend files. Adapted from https://github.com/tiangolo/fastapi/issues/130 + """ + # check first if requested file exists + path = request.path_params["catchall"] + file = WEBAPP_DIRECTORY / Path(path) + if file.exists(): + return FileResponse(file) + + # otherwise return the index + return FileResponse(WEBAPP_DIRECTORY / "index.html") + + def start_webserver() -> None: "Run the webserver." server = Server(Config(app, host="0.0.0.0", port=8080, log_level=WARNING)) diff --git a/tests/core/test_api.py b/tests/core/test_api.py index 5a4c077952..194be70158 100644 --- a/tests/core/test_api.py +++ b/tests/core/test_api.py @@ -179,3 +179,10 @@ def test_visualize(test_config: FidesctlConfig, resource_type: str) -> None: f"{test_config.cli.server_url}/{resource_type}/visualize/graphs" ) assert response.status_code == 200 + + +@pytest.mark.integration +def test_static_sink(test_config: FidesctlConfig) -> None: + """Make sure we are hosting something at / and not getting a 404""" + response = requests.get(f"{test_config.cli.server_url}") + assert response.status_code == 200