Skip to content

Commit

Permalink
Reorganise Bouncer's Gunicorn config
Browse files Browse the repository at this point in the history
This reorganise's Bouncer's Gunicorn config files following the same pattern
as was recently done in h:

hypothesis/h#8407

I'm applying this simpler and more explicit Gunicorn config approach to
all our apps consistently. For motivation see:

https://docs.google.com/document/d/13AnUPtu9AO3PfRm-fhH7_3RZzyLGSFd4uPv36dIeynA

Benefits:

* Gunicorn config files are explicitly mentioned in the `gunicorn`
  commands in the `supervisord[-dev].conf` files. This makes things less
  confusing/surprising, less likely that a Gunicorn config file will be
  missed (compared to Gunicorn's default behavior if you run it without
  a `--config` argument: it reads any `gunicorn.conf.py` file in the
  current working directory).

* The Gunicorn config files are in the `conf/` directory with the other
  config files. Again: makes it less likely that a config file will go
  unnoticed.

* Separate production and development Gunicorn config files. This is
  consistent with our separate production and development Pyramid config
  files, and is probably a good idea to prevent development settings
  accidentally getting applied in production. (Gunicorn's default
  behavior of reading a root `gunicorn.conf.py` file would mean that
  file gets read in both dev and production).
  • Loading branch information
seanh committed Jan 10, 2024
1 parent 2dab787 commit b8382b7
Show file tree
Hide file tree
Showing 17 changed files with 145 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@

node_modules
bouncer/static/scripts/bundle.js
supervisord.log
supervisord.pid
2 changes: 1 addition & 1 deletion bouncer/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def settings():
return result


def app():
def create_app(_=None, **_settings):
"""Configure and return the WSGI app."""
config = pyramid.config.Configurator(settings=settings())
config.add_static_view(name="static", path="static")
Expand Down
30 changes: 30 additions & 0 deletions conf/development.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[app:main]
use = call:bouncer.app:create_app
debug = true

[loggers]
keys = root, bouncer

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = INFO
handlers = console

[logger_bouncer]
level = DEBUG
handlers =
qualname = bouncer

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
5 changes: 5 additions & 0 deletions conf/gunicorn-dev.conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bind = "0.0.0.0:8000"
reload = True
reload_extra_files = "bouncer/templates"
timeout = 0
workers = 2
2 changes: 2 additions & 0 deletions conf/gunicorn.conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bind = "0.0.0.0:8000"
worker_tmp_dir = "/dev/shm"
42 changes: 42 additions & 0 deletions conf/production.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[pipeline:main]
pipeline:
proxy-prefix
bouncer

[app:bouncer]
use = call:bouncer.app:create_app

[filter:proxy-prefix]
use: egg:PasteDeploy#prefix

[loggers]
keys = root, bouncer, alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = INFO
handlers = console

[logger_bouncer]
level = DEBUG
handlers =
qualname = bouncer

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
28 changes: 28 additions & 0 deletions conf/supervisord-dev.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[supervisord]
nodaemon=true
silent=true

[program:web]
command=newrelic-admin run-program gunicorn --paste conf/development.ini --config conf/gunicorn-dev.conf.py
stdout_events_enabled=true
stderr_events_enabled=true
stopsignal=KILL
stopasgroup=true

[eventlistener:logger]
command=bin/logger --dev
buffer_size=100
events=PROCESS_LOG
stderr_logfile=/dev/fd/1
stderr_logfile_maxbytes=0
stdout_logfile=/dev/null

[unix_http_server]
file = .supervisor.sock

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl = unix://.supervisor.sock
prompt = bouncer
2 changes: 1 addition & 1 deletion conf/supervisord.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ logfile=/dev/null
logfile_maxbytes=0

[program:web]
command=gunicorn -b 0.0.0.0:8000 bouncer.app:app()
command=newrelic-admin run-program gunicorn --paste conf/production.ini --config conf/gunicorn.conf.py
stdout_logfile=NONE
stderr_logfile=NONE
stdout_events_enabled=true
Expand Down
1 change: 1 addition & 0 deletions requirements/dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pip-tools
pip-sync-faster
ipython
ipdb
supervisor
-r requirements.txt
21 changes: 19 additions & 2 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is autogenerated by pip-compile with python 3.8
# To update, run:
# This file is autogenerated by pip-compile with Python 3.8
# by the following command:
#
# pip-compile --allow-unsafe requirements/dev.in
#
Expand Down Expand Up @@ -64,6 +64,8 @@ markupsafe==1.1.1
# pyramid-jinja2
matplotlib-inline==0.1.2
# via ipython
newrelic==9.4.0
# via -r requirements/requirements.txt
packaging==23.1
# via
# -r requirements/requirements.txt
Expand Down Expand Up @@ -121,6 +123,8 @@ six==1.16.0
# via asttokens
stack-data==0.1.3
# via ipython
supervisor==4.2.5
# via -r requirements/dev.in
tomli==2.0.1
# via
# build
Expand Down Expand Up @@ -166,3 +170,16 @@ zope-interface==4.7.2
# via
# -r requirements/requirements.txt
# pyramid

# The following packages are considered to be unsafe in a requirements file:
pip==23.3.2
# via pip-tools
setuptools==69.0.3
# via
# -r requirements/requirements.txt
# pip-tools
# plaster
# pyramid
# supervisor
# zope-deprecation
# zope-interface
2 changes: 2 additions & 0 deletions requirements/functests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ markupsafe==1.1.1
# -r requirements/requirements.txt
# jinja2
# pyramid-jinja2
newrelic==9.4.0
# via -r requirements/requirements.txt
packaging==23.1
# via
# -r requirements/requirements.txt
Expand Down
1 change: 1 addition & 0 deletions requirements/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ h-pyramid-sentry
pyramid
pyramid-jinja2
requests
newrelic
2 changes: 2 additions & 0 deletions requirements/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ markupsafe==1.1.1
# via
# jinja2
# pyramid-jinja2
newrelic==9.4.0
# via -r requirements/requirements.in
packaging==23.1
# via gunicorn
pastedeploy==2.1.0
Expand Down
2 changes: 2 additions & 0 deletions requirements/tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ markupsafe==1.1.1
# pyramid-jinja2
mock==5.1.0
# via -r requirements/tests.in
newrelic==9.4.0
# via -r requirements/requirements.txt
packaging==23.1
# via
# -r requirements/requirements.txt
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
from webtest import TestApp

from bouncer.app import app as create_app
from bouncer.app import create_app


@pytest.fixture
Expand Down
10 changes: 5 additions & 5 deletions tests/unit/bouncer/app_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import pytest
from pyramid.config import Configurator

from bouncer.app import app
from bouncer.app import create_app


def test_the_default_settings(config, pyramid):
app()
create_app()

pyramid.config.Configurator.assert_called_once_with(
settings={
Expand All @@ -35,7 +35,7 @@ def test_the_default_settings(config, pyramid):
def test_chrome_extension_id(config, os, envvar, extension_id, pyramid):
os.environ["CHROME_EXTENSION_ID"] = envvar

app()
create_app()

settings = pyramid.config.Configurator.call_args_list[0][1]["settings"]
assert settings["chrome_extension_id"] == extension_id
Expand All @@ -47,7 +47,7 @@ def test_raises_if_chrome_extension_id_invalid(config, os, pyramid):
with pytest.raises(
Exception, match='CHROME_EXTENSION_ID map must have a "default" key'
):
app()
create_app()


@pytest.mark.parametrize(
Expand All @@ -62,7 +62,7 @@ def test_raises_if_chrome_extension_id_invalid(config, os, pyramid):
def test_via_base_url(config, os, envvar, base_url, pyramid):
os.environ["VIA_BASE_URL"] = envvar

app()
create_app()

settings = pyramid.config.Configurator.call_args_list[0][1]["settings"]
assert settings["via_base_url"] == base_url
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ depends =
coverage: tests
commands =
pip-sync-faster requirements/{env:TOX_ENV_NAME}.txt --pip-args '--disable-pip-version-check'
dev: {posargs:gunicorn --reload "bouncer.app:app()"}
dev: {posargs:supervisord -c conf/supervisord-dev.conf}
lint: flake8 .
format: black bouncer tests
format: isort --quiet --atomic bouncer tests
Expand Down

0 comments on commit b8382b7

Please sign in to comment.