Skip to content

Commit

Permalink
Add: dockerize this repository and make sure that it can run on AWS
Browse files Browse the repository at this point in the history
This involves a lot of small changes:
- Add sentry support
- Add GitHub Actions auto-deployment
- Add Dockerfile
- Patch werkzeug to only log non-2NN/3NN
- Readme full of badges
  • Loading branch information
TrueBrain committed Apr 18, 2020
1 parent 8feedf0 commit 6df67de
Show file tree
Hide file tree
Showing 15 changed files with 307 additions and 57 deletions.
3 changes: 3 additions & 0 deletions .Dockerignore
@@ -0,0 +1,3 @@
__pycache__/
*.pyc
/.env
41 changes: 41 additions & 0 deletions .github/workflows/deployment.yml
@@ -0,0 +1,41 @@
name: Deployment

on:
deployment:

jobs:
deploy_to_aws:
name: Deploy to AWS
runs-on: ubuntu-latest

steps:
- name: Deployment in progress
uses: openttd/actions/deployments-update@v1
with:
github-token: ${{ secrets.DEPLOYMENT_TOKEN }}
state: in_progress
description: "Deployment of ${{ github.event.deployment.payload.version }} to ${{ github.event.deployment.environment }} started"

- name: Deploy on AWS
uses: openttd/actions/deploy-aws@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.AWS_REGION }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
name: BananasFrontendWeb

- if: success()
name: Deployment successful
uses: openttd/actions/deployments-update@v1
with:
github-token: ${{ secrets.DEPLOYMENT_TOKEN }}
state: success
description: "Successfully deployed ${{ github.event.deployment.payload.version }} on ${{ github.event.deployment.environment }}"
url: "https://bananas.staging.openttd.org/"

- if: failure() || cancelled()
name: Deployment failed
uses: openttd/actions/deployments-update@v1
with:
github-token: ${{ secrets.DEPLOYMENT_TOKEN }}
state: failure
68 changes: 68 additions & 0 deletions .github/workflows/publish.yml
@@ -0,0 +1,68 @@
name: Publish image

on:
push:
branches:
- master
tags:
- '*'
repository_dispatch:
types:
- publish_latest_tag
- publish_master

jobs:
publish_image:
name: Publish image
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0

- if: github.event_name == 'repository_dispatch'
name: Repository dispatch
uses: openttd/actions/checkout-dispatch@v1

- name: Checkout tags and submodules
uses: openttd/actions/checkout@v1
with:
with-tags: true

- name: Set variables
id: vars
uses: openttd/actions/docker-vars@v1
with:
docker-hub-username: ${{ secrets.DOCKER_USERNAME }}

- name: Build
uses: openttd/actions/docker-build@v1
with:
name: ${{ steps.vars.outputs.name }}
tag: ${{ steps.vars.outputs.tag }}
tags: ${{ steps.vars.outputs.tags }}
version: ${{ steps.vars.outputs.version }}
date: ${{ steps.vars.outputs.date }}

- if: steps.vars.outputs.dry-run == 'false'
name: Publish
id: publish
uses: openttd/actions/docker-publish@v1
with:
docker-hub-username: ${{ secrets.DOCKER_USERNAME }}
docker-hub-password: ${{ secrets.DOCKER_PASSWORD }}
name: ${{ steps.vars.outputs.name }}
tag: ${{ steps.vars.outputs.tag }}

- if: steps.vars.outputs.dry-run == 'false'
name: Trigger deployment
uses: openttd/actions/deployments-create@v1
with:
ref: ${{ steps.vars.outputs.sha }}
environment: ${{ steps.vars.outputs.environment }}
version: ${{ steps.vars.outputs.version }}
date: ${{ steps.vars.outputs.date }}
docker-tag: ${{ steps.publish.outputs.remote-tag }}
github-token: ${{ secrets.DEPLOYMENT_TOKEN }}
1 change: 1 addition & 0 deletions .version
@@ -0,0 +1 @@
dev
32 changes: 32 additions & 0 deletions Dockerfile
@@ -0,0 +1,32 @@
FROM python:3.8-slim

ARG BUILD_DATE=""
ARG BUILD_VERSION="dev"

LABEL maintainer="truebrain@openttd.org"
LABEL org.label-schema.schema-version="1.0"
LABEL org.label-schema.build-date=${BUILD_DATE}
LABEL org.label-schema.version=${BUILD_VERSION}

WORKDIR /code

COPY requirements.txt \
LICENSE \
README.md \
.version \
/code/
# Needed for Sentry to know what version we are running
RUN echo "${BUILD_VERSION}" > /code/.version

RUN pip --no-cache-dir install -r requirements.txt

# Validate that what was installed was what was expected
RUN pip freeze 2>/dev/null > requirements.installed \
&& diff -u --strip-trailing-cr requirements.txt requirements.installed 1>&2 \
|| ( echo "!! ERROR !! requirements.txt defined different packages or versions for installation" \
&& exit 1 ) 1>&2

COPY webclient /code/webclient

ENTRYPOINT ["python", "-m", "webclient"]
CMD ["--authentication-method", "developer", "--developer-username", "developer", "--api-url", "http://127.0.0.1:8080", "--frontend-url", "https://127.0.0.1:5000", "run", "-p", "80", "-h", "0.0.0.0"]
20 changes: 20 additions & 0 deletions README.md
@@ -1,5 +1,16 @@
# BaNaNaS web front-end

[![GitHub License](https://img.shields.io/github/license/OpenTTD/bananas-frontend-web)](https://github.com/OpenTTD/bananas-frontend-web/blob/master/LICENSE)
[![GitHub Tag](https://img.shields.io/github/v/tag/OpenTTD/bananas-frontend-web?include_prereleases&label=stable)](https://github.com/OpenTTD/bananas-frontend-web/releases)
[![GitHub commits since latest release](https://img.shields.io/github/commits-since/OpenTTD/bananas-frontend-web/latest/master)](https://github.com/OpenTTD/bananas-frontend-web/commits/master)

[![GitHub Workflow Status (Testing)](https://img.shields.io/github/workflow/status/OpenTTD/bananas-frontend-web/Testing/master?label=master)](https://github.com/OpenTTD/bananas-frontend-web/actions?query=workflow%3ATesting)
[![GitHub Workflow Status (Publish Image)](https://img.shields.io/github/workflow/status/OpenTTD/bananas-frontend-web/Publish%20image?label=publish)](https://github.com/OpenTTD/bananas-frontend-web/actions?query=workflow%3A%22Publish+image%22)
[![GitHub Workflow Status (Deployments)](https://img.shields.io/github/workflow/status/OpenTTD/bananas-frontend-web/Deployment?label=deployment)](https://github.com/OpenTTD/bananas-frontend-web/actions?query=workflow%3A%22Deployment%22)

[![GitHub deployments (Staging)](https://img.shields.io/github/deployments/OpenTTD/bananas-frontend-web/staging?label=staging)](https://github.com/OpenTTD/bananas-frontend-web/deployments)
[![GitHub deployments (Production)](https://img.shields.io/github/deployments/OpenTTD/bananas-frontend-web/production?label=production)](https://github.com/OpenTTD/bananas-frontend-web/deployments)

This is a front-end for browsing and upload content to OpenTTD's content service, called BaNaNaS.
It works together with [bananas-api](https://github.com/OpenTTD/bananas-api), which serves the HTTP API.

Expand All @@ -21,3 +32,12 @@ After this, you can run the flask application by running:
```bash
make run
```

### Running via docker

```bash
docker build -t openttd/bananas-frontend-web:local .
docker run --rm -p 127.0.0.1:5000:80 openttd/bananas-frontend-web:local
```

The mount assumes that [bananas-api](https://github.com/OpenTTD/bananas-api) is already running on the system.
1 change: 1 addition & 0 deletions requirements.base
@@ -1,2 +1,3 @@
Flask
requests
sentry_sdk
3 changes: 2 additions & 1 deletion requirements.txt
Expand Up @@ -7,5 +7,6 @@ itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
requests==2.23.0
urllib3==1.25.8
sentry-sdk==0.14.3
urllib3==1.25.9
Werkzeug==1.0.1
76 changes: 33 additions & 43 deletions webclient/__main__.py
@@ -1,56 +1,46 @@
import click
import datetime
import flask
import logging

from werkzeug import serving

from . import pages # noqa
from .app import app
from .helpers import (
set_api_url,
set_frontend_url,
)
from .click import click_additional_options
from .helpers import click_urls
from .sentry import click_sentry
from .session import (
set_auth_backend,
set_max_age,
click_auth_backend,
click_max_age,
)


# Patch the werkzeug logger to only log errors
def log_request(self, code="-", size="-"):
if str(code).startswith(("2", "3")):
return
original_log_request(self, code, size)


original_log_request = serving.WSGIRequestHandler.log_request
serving.WSGIRequestHandler.log_request = log_request


@click_additional_options
def click_logging():
logging.basicConfig(
format="%(asctime)s %(levelname)-8s %(message)s", datefmt="%Y-%m-%d %H:%M:%S", level=logging.INFO
)


@click.group(cls=flask.cli.FlaskGroup, create_app=lambda: app)
@click.option(
"--api-url", help="BaNaNaS API URL.", default="https://api.bananas.openttd.org", show_default=True, metavar="URL",
)
@click.option(
"--frontend-url",
help="Frontend URL (this server).",
default="https://bananas.openttd.org",
show_default=True,
metavar="URL",
)
@click.option(
"--authentication-method",
help="Authentication method to use.",
type=click.Choice(["developer", "github", "openttd"], case_sensitive=False),
default="github",
show_default=True,
)
@click.option("--developer-username", help="Username to use if authentication is set to 'developer'.")
@click.option(
"--session-expire",
help="Time for a session to expire.",
default=60 * 60 * 14,
show_default=True,
metavar="SECONDS",
)
@click.option(
"--csrf-expire", help="Time for the CSRF token to expire.", default=60 * 30, show_default=True, metavar="SECONDS",
)
def cli(api_url, frontend_url, authentication_method, developer_username, session_expire, csrf_expire):
if authentication_method == "developer" and not developer_username:
raise click.UsageError("'developer-username' should be set if 'authentication-method' is 'developer'")

set_api_url(api_url)
set_frontend_url(frontend_url)
set_auth_backend(authentication_method, developer_username)
set_max_age(datetime.timedelta(seconds=session_expire), datetime.timedelta(seconds=csrf_expire))
@click_logging
@click_sentry
@click_urls
@click_auth_backend
@click_max_age
def cli():
pass


if __name__ == "__main__":
Expand Down
22 changes: 22 additions & 0 deletions webclient/click.py
@@ -0,0 +1,22 @@
def click_additional_options(additional_func):
def decorator(func):
additional_params = []
for param in getattr(additional_func, "__click_params__", []):
additional_params.append(param.name)

def inner_decorator(**kwargs):
additional_kwargs = {param: kwargs[param] for param in additional_params}
additional_func(**additional_kwargs)

# Remove the kwargs that are consumed by the additional_func
[kwargs.pop(kwarg) for kwarg in additional_kwargs]

func(**kwargs)

inner_decorator.__click_params__ = getattr(func, "__click_params__", []) + getattr(
additional_func, "__click_params__", []
)
inner_decorator.__doc__ = func.__doc__
return inner_decorator

return decorator
25 changes: 17 additions & 8 deletions webclient/helpers.py
@@ -1,21 +1,30 @@
import click
import flask
import requests
import urllib

from .app import app
from .click import click_additional_options

_api_url = None
_frontend_url = None


def set_api_url(url):
global _api_url
_api_url = url


def set_frontend_url(url):
global _frontend_url
_frontend_url = url
@click_additional_options
@click.option(
"--api-url", help="BaNaNaS API URL.", default="https://api.bananas.openttd.org", show_default=True, metavar="URL",
)
@click.option(
"--frontend-url",
help="Frontend URL (this server).",
default="https://bananas.openttd.org",
show_default=True,
metavar="URL",
)
def click_urls(api_url, frontend_url):
global _api_url, _frontend_url
_api_url = api_url
_frontend_url = frontend_url


def template(*args, **kwargs):
Expand Down
9 changes: 9 additions & 0 deletions webclient/pages/static.py
@@ -1,3 +1,5 @@
import flask

from ..app import app
from ..helpers import (
not_found,
Expand All @@ -13,6 +15,13 @@ def root():
return template("main.html")


@app.route("/healthz")
def healthz_handler():
response = flask.make_response("200: OK")
response.headers["Content-Type"] = "text/plain"
return response


@app.route("/manager/tos")
def tos_latest():
return redirect("tos", version="1.2")
Expand Down
26 changes: 26 additions & 0 deletions webclient/sentry.py
@@ -0,0 +1,26 @@
import click
import logging
import sentry_sdk

from .click import click_additional_options

log = logging.getLogger(__name__)


@click_additional_options
@click.option("--sentry-dsn", help="Sentry DSN.")
@click.option(
"--sentry-environment", help="Environment we are running in.", default="development",
)
def click_sentry(sentry_dsn, sentry_environment):
if not sentry_dsn:
return

# Release is expected to be in the file '.version'
with open(".version") as f:
release = f.readline().strip()

sentry_sdk.init(sentry_dsn, release=release, environment=sentry_environment)
log.info(
"Sentry initialized with release='%s' and environment='%s'", release, sentry_environment,
)

0 comments on commit 6df67de

Please sign in to comment.