From bfc0c2d9943bb5e5128eaab6edd257316419e40a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hrn=C4=8Diar?= <thrnciar@redhat.com>
Date: Mon, 3 Jun 2024 15:38:21 +0200
Subject: [PATCH] Allow the usage of gunicorn.conf.py config file in gunicorn
 applications

Fixes: #599
---
 .../.gitignore                                | 57 +++++++++++++++++++
 .../gunicorn-using-configfile-test-app/app.py |  4 ++
 .../gunicorn.conf.py                          |  6 ++
 .../requirements.txt                          |  1 +
 manifest-minimal.yml                          |  3 +
 manifest.yml                                  |  3 +
 src/s2i/bin/run                               | 16 ++++++
 test/run                                      |  2 +-
 8 files changed, 91 insertions(+), 1 deletion(-)
 create mode 100644 examples/gunicorn-using-configfile-test-app/.gitignore
 create mode 100644 examples/gunicorn-using-configfile-test-app/app.py
 create mode 100644 examples/gunicorn-using-configfile-test-app/gunicorn.conf.py
 create mode 100644 examples/gunicorn-using-configfile-test-app/requirements.txt

diff --git a/examples/gunicorn-using-configfile-test-app/.gitignore b/examples/gunicorn-using-configfile-test-app/.gitignore
new file mode 100644
index 00000000..ba746605
--- /dev/null
+++ b/examples/gunicorn-using-configfile-test-app/.gitignore
@@ -0,0 +1,57 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
diff --git a/examples/gunicorn-using-configfile-test-app/app.py b/examples/gunicorn-using-configfile-test-app/app.py
new file mode 100644
index 00000000..09fee1b7
--- /dev/null
+++ b/examples/gunicorn-using-configfile-test-app/app.py
@@ -0,0 +1,4 @@
+
+def application(environ, start_response):
+    start_response('200 OK', [('Content-Type','text/plain')])
+    return [b"Hello from gunicorn WSGI application!"]
diff --git a/examples/gunicorn-using-configfile-test-app/gunicorn.conf.py b/examples/gunicorn-using-configfile-test-app/gunicorn.conf.py
new file mode 100644
index 00000000..bcb267f6
--- /dev/null
+++ b/examples/gunicorn-using-configfile-test-app/gunicorn.conf.py
@@ -0,0 +1,6 @@
+import multiprocessing
+
+bind = "0.0.0.0:8085"
+workers = multiprocessing.cpu_count() * 2 + 1
+
+wsgi_app = "app:application"
diff --git a/examples/gunicorn-using-configfile-test-app/requirements.txt b/examples/gunicorn-using-configfile-test-app/requirements.txt
new file mode 100644
index 00000000..b0d8963f
--- /dev/null
+++ b/examples/gunicorn-using-configfile-test-app/requirements.txt
@@ -0,0 +1 @@
+gunicorn>=20.1.0; python_version >= '3.5'
diff --git a/manifest-minimal.yml b/manifest-minimal.yml
index 91c2e9fa..4ea981c5 100644
--- a/manifest-minimal.yml
+++ b/manifest-minimal.yml
@@ -142,6 +142,9 @@ SYMLINK_RULES:
   - src: ../../examples/gunicorn-config-different-port-test-app
     dest: test/gunicorn-config-different-port-test-app
 
+  - src: ../../examples/gunicorn-using-configfile-test-app
+    dest: test/gunicorn-using-configfile-test-app
+
   - src: ../../examples/locale-test-app
     dest: test/locale-test-app
 
diff --git a/manifest.yml b/manifest.yml
index 9e71f094..45c1dbc8 100644
--- a/manifest.yml
+++ b/manifest.yml
@@ -141,6 +141,9 @@ SYMLINK_RULES:
   - src: ../../examples/gunicorn-config-different-port-test-app
     dest: test/gunicorn-config-different-port-test-app
 
+  - src: ../../examples/gunicorn-using-configfile-test-app
+    dest: test/gunicorn-using-configfile-test-app
+
   - src: ../../examples/locale-test-app
     dest: test/locale-test-app
 
diff --git a/src/s2i/bin/run b/src/s2i/bin/run
index 2a982cd1..98b9307e 100755
--- a/src/s2i/bin/run
+++ b/src/s2i/bin/run
@@ -49,6 +49,22 @@ function maybe_run_in_init_wrapper() {
   fi
 }
 
+# Look for gunicorn>=20.1.0 to utilize gunicorn.conf.py
+if is_gunicorn_installed && [[ -f "gunicorn.conf.py" ]]; then
+  python -c 'import gunicorn
+ver = gunicorn.version_info
+if (ver[0] >= 20 and ver[1] >= 1) or ver[0] > 20:
+    exit(0)'
+  ret=$?
+  grep -q "wsgi_app" gunicorn.conf.py
+  grep_result=$?
+  if [[ $ret -eq 0 ]] && [[ -f "gunicorn.conf.py" ]] && [[ $grep_result -eq 0 ]]; then
+    echo "--> Using gunicorn.conf.py"
+  fi
+  echo "---> Serving application with gunicorn ..."
+  exec gunicorn
+fi
+
 APP_HOME=$(readlink -f "${APP_HOME:-.}")
 # Change the working directory to APP_HOME
 PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}"
diff --git a/test/run b/test/run
index f0d50c67..563b717e 100755
--- a/test/run
+++ b/test/run
@@ -6,7 +6,7 @@
 # IMAGE_NAME specifies a name of the candidate image used for testing.
 # The image has to be available before this script is executed.
 #
-declare -a COMMON_WEB_APPS=({gunicorn-config-different-port,gunicorn-different-port,django-different-port,standalone,setup,setup-requirements,django,numpy,app-home,locale,pipenv,pipenv-and-micropipenv-should-fail,app-module,pyuwsgi-pipenv{% if spec.version.startswith("3.") %},micropipenv,standalone-custom-pypi-index{% endif %}}-test-app)
+declare -a COMMON_WEB_APPS=({gunicorn-config-different-port,gunicorn-different-port,gunicorn-using-configfile,django-different-port,standalone,setup,setup-requirements,django,numpy,app-home,locale,pipenv,pipenv-and-micropipenv-should-fail,app-module,pyuwsgi-pipenv{% if spec.version.startswith("3.") %},micropipenv,standalone-custom-pypi-index{% endif %}}-test-app)
 declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version{% if spec.version.startswith("3.") %},micropipenv-requirements,poetry-src-layout{% endif %}}-test-app)
 declare -a MINIMAL_WEB_APPS=()
 {% if spec.minimal %}