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 d1bbd67c7a..776b57d652 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`
@@ -35,6 +39,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
@@ -48,7 +53,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 2e40e9f721..2748b847a2 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