Skip to content

Commit

Permalink
added doc mode + added image tests + added docs about doc mode (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
bentheiii authored Feb 14, 2022
1 parent 260c194 commit d23b29c
Show file tree
Hide file tree
Showing 14 changed files with 204 additions and 301 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
* The api endpoint PUT /api/v1/rules/<rule>/value to change a rule's value
* The api endpoint GET /api/v1/query to query rules (replaces the old query endpoint)
* POST /api/v1/rules now returns the rule location in the header
* added DOC_ONLY mode, read more about in the documentation
### Fixed
* A bug where patching a context feature's index using "to_before" would use the incorrect target.
### Internal
* a new script to test and correctly report coverage
* tools/mk_revision.py to easily create alembic revisions
* all db logic refactored to avoid multiple connections
* Many more column are now strictly non-nullable
* async-asgi-testclient is now a dev-dependency.
* added proper image tests
## 0.4.1
### Removed
* removed the alembic extra, it's now a requirement
Expand Down
15 changes: 7 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8
FROM python:3.8

RUN apt-get update && \
apt-get -y install gcc build-essential
Expand All @@ -7,11 +7,10 @@ RUN mkdir -p /usr/src/app/heksher

WORKDIR /usr/src/app/heksher

RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python && \
cd /usr/local/bin && \
ln -s /opt/poetry/bin/poetry && \
poetry config virtualenvs.create false
COPY pyproject.toml poetry.lock* ./
RUN pip install pip --upgrade
RUN pip install poetry
RUN poetry config virtualenvs.create false
COPY pyproject.toml poetry.lock ./
RUN poetry run pip install --upgrade pip
RUN poetry install --no-dev --no-root

Expand All @@ -21,5 +20,5 @@ RUN export APP_VERSION=$(poetry version | cut -d' ' -f2) && echo "__version__ =

ENV PYTHONPATH=${PYTHONPATH}:/usr/src/app/heksher
ENV PYTHONOPTIMIZE=1
ENV WEB_CONCURRENCY=1
ENV MODULE_NAME=heksher.main

CMD ["uvicorn", "heksher.main:app", "--host", "0.0.0.0", "--port", "80"]
20 changes: 15 additions & 5 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ Since Heksher is a FastAPI service, the API can also be accessed via the redoc e
The most common endpoints for users are :ref:`setting declaration <api:POST /api/v1/settings/declare>`,
and :ref:`querying <api:GET /api/v1/query>`

.. note::

You can view the api in a swagger or redoc-style format locally by running heksher in :ref:`running:Doc Only Mode`

.. code-block:: console
docker run -d -p 9999:80 --name heksher-doc-only -e DOC_ONLY=true biocatchltd/heksher
and accessing http://localhost:9999/redoc or http://localhost:9999/docs

General
-------

Expand Down Expand Up @@ -399,8 +409,8 @@ Response:
* **metadata**: A dictionary of metadata associated with the setting. Only included if include_additional_data is true.
* **aliases**: A list aliases of the setting. Only included if include_additional_data is true.

PUT /api/v1/settings/<name>/type
********************************
PUT /api/v1/settings/<setting_name>/type
*******************************************

Change a setting's type in a way that is not necessarily backwards compatible.

Expand All @@ -416,8 +426,8 @@ If there are type conflicts, the 409 response will have the schema:

* **conflicts**: A list of strings describing the conflicts.

PUT /api/v1/settings/<name>/name
*********************************
PUT /api/v1/settings/<setting_name>/name
*********************************************

Rename a setting.

Expand All @@ -432,7 +442,7 @@ alias to the setting and an empty 204 response will be returned.
If the new name is already in use, or if the version is incompatible with the latest declaration, a 409 response will
be returned.

PUT /api/v1/settings/setting_name>/configurable_features
PUT /api/v1/settings/<setting_name>/configurable_features
***********************************************************

Change the configurable features of a setting.
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ attributes:
* metadata: additional metadata about the setting.
* aliases: a list of :ref:`aliases <setting_aliases:Setting Aliases>` of the setting.
* default: the default value of the setting.
* version: the latest version of the setting declaration, see :ref:`setting_versioning:Setting Versioning`.
* version: the latest version of the setting declaration, see :ref:`setting_versions:Setting Versions`.

Context Features
-----------------------
Expand Down
19 changes: 15 additions & 4 deletions docs/running.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Heksher is an HTTP service, running it is best done through the

.. code-block:: console
docker run -d -p 80:80 --name heksher biocatchltd/heksher -E ...
docker run -d -p 80:80 --name heksher -e ... biocatchltd/heksher
Dependencies
-----------------
Expand All @@ -15,15 +15,15 @@ environment variable ``HEKSHER_DB_CONNECTION_STRING`` as a driverless sqlalchemy

.. code-block:: console
docker run -d -p 80:80 --name heksher biocatchltd/heksher -E HEKSHER_DB_CONNECTION_STRING=postgresql://user:password@host:port/dbname -E ...
docker run -d -p 80:80 --name heksher -e HEKSHER_DB_CONNECTION_STRING=postgresql://user:password@host:port/dbname -e ... biocatchltd/heksher
The database must be initialized to Heksher's schema. the database's schema is handled with
`alembic <https://alembic.sqlalchemy.org/en/latest/>`_. For convenience, the database can be initialized with
alembic using the Heksher image.

.. code-block:: console
docker run biocatchltd/heksher alembic upgrade head -E HEKSHER_DB_CONNECTION_STRING=postgresql://user:password@host:port/dbname
docker run -e HEKSHER_DB_CONNECTION_STRING=postgresql://user:password@host:port/dbname biocatchltd/heksher alembic upgrade head
.. note::

Expand All @@ -43,4 +43,15 @@ The following environment variables are optional for logging:
* **HEKSHER_LOGSTASH_HOST**: the logstash host to send logs to.
* **HEKSHER_LOGSTASH_PORT**: the logstash port to send logs to.
* **HEKSHER_LOGSTASH_LEVEL**: the log level to send logs on.
* **HEKSHER_LOGSTASH_TAGS**: additional tags to send with logs.
* **HEKSHER_LOGSTASH_TAGS**: additional tags to send with logs.

Doc Only Mode
------------------------

Heksher also has a doc-only mode, where all routes and endpoints are disabled, except fastapi's standard doc pages:
``/redoc`` and ``/docs``. This mode can enabled by passing the environment variable ``DOC_ONLY=true``. If doc-mode is
enabled, no connection to any underlying dependency is made, and the service will start up even if all other environment
variables are missing.

When in doc-only mode, attempting to access any api in heksher other than those above (and ``/api/health``) will result
in a 500 error code.
5 changes: 3 additions & 2 deletions docs/setting_versions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ the latest declared version of the setting.
latest declaration. If the assertion fails, we inform the user of an attribute mismatch.
* If the latest declared version is higher than the declaration version, we inform the user that they are declaring with
outdated attributes.

.. warning::

Differing attributes are not checked for older versions. If a user purposely declares a setting with an older
Expand Down Expand Up @@ -60,5 +61,5 @@ might fail depending on the state of the ruleset of the service. If these confli
API, our app might fail. To avoid this, these potentially conflicting changes can be made explicit with explicit API
calls. These API endpoints are:

* :ref:`PUT /api/v1/settings/setting_name>/configurable_features`
* :ref:`PUT /api/v1/settings/setting_name>/type`
* :ref:`api:PUT /api/v1/settings/<setting_name>/configurable_features`
* :ref:`api:PUT /api/v1/settings/<setting_name>/type`
28 changes: 27 additions & 1 deletion heksher/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
from aiologstash import create_tcp_handler
from envolved import EnvVar, Schema
from envolved.parsers import CollectionParser
from fastapi import FastAPI
from fastapi import FastAPI, Request
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
from starlette.responses import PlainTextResponse
from starlette.status import HTTP_404_NOT_FOUND

from heksher._version import __version__
from heksher.db_logic.context_feature import db_add_context_features, db_get_context_features, db_move_context_features
Expand All @@ -21,6 +23,7 @@

connection_string = EnvVar('HEKSHER_DB_CONNECTION_STRING', type=db_url_with_async_driver)
startup_context_features = EnvVar('HEKSHER_STARTUP_CONTEXT_FEATURES', type=CollectionParser(';', str), default=None)
doc_only_ev = EnvVar("DOC_ONLY", type=bool, default=False)


class LogstashSettingSchema(Schema):
Expand All @@ -34,13 +37,22 @@ class LogstashSettingSchema(Schema):
logstash_settings_ev = EnvVar('HEKSHER_LOGSTASH_', default=None, type=LogstashSettingSchema)
sentry_dsn_ev = EnvVar('SENTRY_DSN', default='', type=str)

redoc_mode_whitelist = frozenset((
'/favicon.ico',
'/docs',
'/redoc',
'/openapi.json',
'/api/health',
))


class HeksherApp(FastAPI):
"""
The application class
"""
engine: AsyncEngine
health_monitor: HealthMonitor
doc_only: bool

async def ensure_context_features(self, expected_context_features: Sequence[str]):
async with self.engine.connect() as conn:
Expand All @@ -64,6 +76,20 @@ async def ensure_context_features(self, expected_context_features: Sequence[str]
await db_add_context_features(conn, dict(super_sequence))

async def startup(self):
self.doc_only = doc_only_ev.get()
if self.doc_only:
@self.middleware('http')
async def doc_only_middleware(request: Request, call_next):
path = request.url.path
if path.endswith("/"):
# our whitelist is without a trailing slash. we remove the slash here and let starlette's redirect
# do its work
path = path[:-1]
if path not in redoc_mode_whitelist:
return PlainTextResponse("The server is running in doc_only mode, only docs/ and redoc/ paths"
" are supported", HTTP_404_NOT_FOUND)
return await call_next(request)
return
logstash_settings = logstash_settings_ev.get()
if logstash_settings is not None:
handler = await create_tcp_handler(logstash_settings.host, logstash_settings.port, extra={
Expand Down
2 changes: 2 additions & 0 deletions heksher/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ async def health_check():
"""
Check the health of the connections to the service
"""
if app.doc_only:
return JSONResponse({'version': __version__, 'doc_only': True})
if not app.health_monitor.status:
return JSONResponse({'version': __version__}, status_code=500)
return {'version': __version__}
Expand Down
Loading

0 comments on commit d23b29c

Please sign in to comment.