diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ceec22e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,68 @@ +# This image provides a Python 3.5 environment you can use to run your Python +# applications. +FROM registry.fedoraproject.org/f26/s2i-base:latest + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.6 \ + PATH=$HOME/.local/bin/:$PATH \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + PIP_NO_CACHE_DIR=off + +ENV NAME=python3 \ + VERSION=0 \ + RELEASE=1 \ + ARCH=x86_64 + +ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as docker container is a base platform for \ +building and running various Python $PYTHON_VERSION applications and frameworks. \ +Python is an easy to learn, powerful programming language. It has efficient high-level \ +data structures and a simple but effective approach to object-oriented programming. \ +Python's elegant syntax and dynamic typing, together with its interpreted nature, \ +make it an ideal language for scripting and rapid application development in many areas \ +on most platforms." + +LABEL summary="$SUMMARY" \ + description="$DESCRIPTION" \ + io.k8s.description="$DESCRIPTION" \ + io.k8s.display-name="Python 3.6" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python36,rh-python36" \ + com.redhat.component="$NAME" \ + name="$FGC/$NAME" \ + version="$VERSION" \ + release="$RELEASE.$DISTTAG" \ + architecture="$ARCH" \ + usage="s2i build file:///your/app 26/python3 your-app" \ + maintainer="SoftwareCollections.org " + +RUN INSTALL_PKGS="python3 python3-devel python3-setuptools python3-pip python3-virtualenv \ + nss_wrapper httpd httpd-devel atlas-devel gcc-gfortran libffi-devel libtool-ltdl" && \ + dnf install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + dnf clean all -y + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY ./s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY ./root/ / + +# Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +RUN virtualenv-$PYTHON_VERSION /opt/app-root + +# In order to drop the root user, we have to make some directories world +# writable as OpenShift default security model is to run the container under +# random UID. +RUN chown -R 1001:0 /opt/app-root && chmod -R og+rwx /opt/app-root + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/README.md b/README.md new file mode 100644 index 0000000..a7604ec --- /dev/null +++ b/README.md @@ -0,0 +1,246 @@ +Python Docker image +=================== + +This repository contains the source for building various versions of +the Python application as a reproducible Docker image using +[source-to-image](https://github.com/openshift/source-to-image). +Users can choose between RHEL and CentOS based builder images. +The resulting image can be run using [Docker](http://docker.io). + + +Usage +--------------------- +To build a simple [python-sample-app](https://github.com/openshift/s2i-python/tree/master/3.6/test/setup-test-app) application +using standalone [S2I](https://github.com/openshift/source-to-image) and then run the +resulting image with [Docker](http://docker.io) execute: + +* **For RHEL based image** + ``` + $ s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.6/test/setup-test-app/ rhscl/python-36-rhel7 python-sample-app + $ docker run -p 8080:8080 python-sample-app + ``` + +* **For CentOS based image** + ``` + $ s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.6/test/setup-test-app/ centos/python-36-centos7 python-sample-app + $ docker run -p 8080:8080 python-sample-app + ``` + +**Accessing the application:** +``` +$ curl 127.0.0.1:8080 +``` + + +Repository organization +------------------------ +* **``** + + * **Dockerfile** + + CentOS based Dockerfile. + + * **Dockerfile.rhel7** + + RHEL based Dockerfile. In order to perform build or test actions on this + Dockerfile you need to run the action on a properly subscribed RHEL machine. + + * **`s2i/bin/`** + + This folder contains scripts that are run by [S2I](https://github.com/openshift/source-to-image): + + * **assemble** + + Used to install the sources into the location where the application + will be run and prepare the application for deployment (eg. installing + dependencies, etc.) + + * **run** + + This script is responsible for running the application by using the + application web server. + + * **usage*** + + This script prints the usage of this image. + + * **`contrib/`** + + This folder contains a file with commonly used modules. + + * **`test/`** + + This folder contains a [S2I](https://github.com/openshift/source-to-image) + test framework with a simple server. + + * **`setup-test-app/`** + + Simple Gunicorn application used for testing purposes by the [S2I](https://github.com/openshift/source-to-image) test framework. + + * **`standalone-test-app/`** + + Simple standalone application used for testing purposes by the [S2I](https://github.com/openshift/source-to-image) test framework. + + * **run** + + Script that runs the [S2I](https://github.com/openshift/source-to-image) test framework. + + +Environment variables +--------------------- + +To set these environment variables, you can place them as a key value pair into a `.s2i/environment` +file inside your source code repository. + +* **APP_SCRIPT** + + Used to run the application from a script file. + This should be a path to a script file (defaults to `app.sh` unless set to null) that will be + run to start the application. + +* **APP_FILE** + + Used to run the application from a Python script. + This should be a path to a Python file (defaults to `app.py` unless set to null) that will be + passed to the Python interpreter to start the application. + +* **APP_MODULE** + + Used to run the application with Gunicorn, as documented + [here](http://docs.gunicorn.org/en/latest/run.html#gunicorn). + This variable specifies a WSGI callable with the pattern + `MODULE_NAME:VARIABLE_NAME`, where `MODULE_NAME` is the full dotted path + of a module, and `VARIABLE_NAME` refers to a WSGI callable inside the + specified module. + Gunicorn will look for a WSGI callable named `application` if not specified. + + If `APP_MODULE` is not provided, the `run` script will look for a `wsgi.py` + file in your project and use it if it exists. + + If using `setup.py` for installing the application, the `MODULE_NAME` part + can be read from there. For an example, see + [setup-test-app](https://github.com/openshift/s2i-python/tree/master/3.6/test/setup-test-app). + +* **APP_HOME** + + This variable can be used to specify a sub-directory in which the application to be run is contained. + The directory pointed to by this variable needs to contain `wsgi.py` (for Gunicorn) or `manage.py` (for Django). + + If `APP_HOME` is not provided, the `assemble` and `run` scripts will use the application's root + directory. + +* **APP_CONFIG** + + Path to a valid Python file with a + [Gunicorn configuration](http://docs.gunicorn.org/en/latest/configure.html#configuration-file) file. + +* **DISABLE_COLLECTSTATIC** + + Set this variable to a non-empty value to inhibit the execution of + 'manage.py collectstatic' during the build. This only affects Django projects. + +* **DISABLE_MIGRATE** + + Set this variable to a non-empty value to inhibit the execution of 'manage.py migrate' + when the produced image is run. This only affects Django projects. + +* **PIP_INDEX_URL** + + Set this variable to use a custom index URL or mirror to download required packages + during build process. This only affects packages listed in requirements.txt. + +* **UPGRADE_PIP_TO_LATEST** + + Set this variable to a non-empty value to have the 'pip' program be upgraded + to the most recent version before any Python packages are installed. If not + set it will use whatever the default version is included by the platform + for the Python version being used. + +* **WEB_CONCURRENCY** + + Set this to change the default setting for the number of + [workers](http://docs.gunicorn.org/en/stable/settings.html#workers). By + default, this is set to the number of available cores times 2. + +Source repository layout +------------------------ + +You do not need to change anything in your existing Python project's repository. +However, if these files exist they will affect the behavior of the build process: + +* **requirements.txt** + + List of dependencies to be installed with `pip`. The format is documented + [here](https://pip.pypa.io/en/latest/user_guide.html#requirements-files). + + +* **setup.py** + + Configures various aspects of the project, including installation of + dependencies, as documented + [here](https://packaging.python.org/en/latest/distributing.html#setup-py). + For most projects, it is sufficient to simply use `requirements.txt`. + + +Run strategies +-------------- + +The Docker image produced by s2i-python executes your project in one of the +following ways, in precedence order: + +* **Gunicorn** + + The Gunicorn WSGI HTTP server is used to serve your application in the case that it + is installed. It can be installed by listing it either in the `requirements.txt` + file or in the `install_requires` section of the `setup.py` file. + + If a file named `wsgi.py` is present in your repository, it will be used as + the entry point to your application. This can be overridden with the + environment variable `APP_MODULE`. + This file is present in Django projects by default. + + If you have both Django and Gunicorn in your requirements, your Django project + will automatically be served using Gunicorn. + +* **Django development server** + + If you have Django in your requirements but don't have Gunicorn, then your + application will be served using Django's development web server. However, this is not + recommended for production environments. + +* **Python script** + + This would be used where you provide a Python code file for running you + application. It will be used in the case where you specify a path to a + Python script via the `APP_FILE` environment variable, defaulting to a + file named `app.py` if it exists. The script is passed to a regular + Python interpreter to launch your application. + +* **Application script file** + + This is the most general way of executing your application. It will be + used in the case where you specify a path to an executable script file + via the `APP_SCRIPT` environment variable, defaulting to a file named + `app.sh` if it exists. The script is executed directly to launch your + application. + +Hot deploy +--------------------- + +If you are using Django, hot deploy will work out of the box. + +To enable hot deploy while using Gunicorn, make sure you have a Gunicorn +configuration file inside your repository with the +[`reload`](https://gunicorn-docs.readthedocs.org/en/latest/settings.html#reload) +option set to `true`. Make sure to specify your config via the `APP_CONFIG` +environment variable. + +To change your source code in running container, use Docker's +[exec](https://docs.docker.com/reference/commandline/exec/) command: + +``` +docker exec -it /bin/bash +``` + +After you enter into the running container, your current directory is set +to `/opt/app-root/src`, where the source code is located. diff --git a/root/opt/app-root/etc/generate_container_user b/root/opt/app-root/etc/generate_container_user new file mode 100644 index 0000000..43ee7ec --- /dev/null +++ b/root/opt/app-root/etc/generate_container_user @@ -0,0 +1,19 @@ +# Set current user in nss_wrapper +USER_ID=$(id -u) +GROUP_ID=$(id -g) + +if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"1001" ]; then + + NSS_WRAPPER_PASSWD=/opt/app-root/etc/passwd + NSS_WRAPPER_GROUP=/etc/group + + cat /etc/passwd | sed -e 's/^default:/builder:/' > $NSS_WRAPPER_PASSWD + + echo "default:x:${USER_ID}:${GROUP_ID}:Default Application User:${HOME}:/sbin/nologin" >> $NSS_WRAPPER_PASSWD + + export NSS_WRAPPER_PASSWD + export NSS_WRAPPER_GROUP + + LD_PRELOAD=libnss_wrapper.so + export LD_PRELOAD +fi diff --git a/root/opt/app-root/etc/scl_enable b/root/opt/app-root/etc/scl_enable new file mode 100644 index 0000000..661da3c --- /dev/null +++ b/root/opt/app-root/etc/scl_enable @@ -0,0 +1,6 @@ +# IMPORTANT: Do not add more content to this file unless you know what you are +# doing. This file is sourced everytime the shell session is opened. +# This will make scl collection binaries work out of box. +unset BASH_ENV PROMPT_COMMAND ENV +source scl_source enable httpd24 rh-python36 +source /opt/app-root/bin/activate diff --git a/s2i/bin/assemble b/s2i/bin/assemble new file mode 100755 index 0000000..846bdbf --- /dev/null +++ b/s2i/bin/assemble @@ -0,0 +1,59 @@ +#!/bin/bash + +function is_django_installed() { + python -c "import django" &>/dev/null +} + +function should_collectstatic() { + is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] +} + +set -e + +shopt -s dotglob +echo "---> Installing application source ..." +mv /tmp/src/* ./ + +if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then + echo "---> Upgrading pip to latest version ..." + pip install -U pip +fi + +if [[ -f requirements.txt ]]; then + echo "---> Installing dependencies ..." + pip install -r requirements.txt +fi + +if [[ -f setup.py ]]; then + echo "---> Installing application ..." + python setup.py develop +fi + +if should_collectstatic; then + ( + echo "---> Collecting Django static files ..." + + + APP_HOME=${APP_HOME:-.} + # Look for 'manage.py' in the directory specified by APP_HOME, or the current directory + manage_file=$APP_HOME/manage.py + + if [[ ! -f "$manage_file" ]]; then + echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." + echo "'manage.py collectstatic' ignored." + exit + fi + + if ! python $manage_file collectstatic --dry-run --noinput &> /dev/null; then + echo "WARNING: could not run 'manage.py collectstatic'. To debug, run:" + echo " $ python $manage_file collectstatic --noinput" + echo "Ignore this warning if you're not serving static files with Django." + exit + fi + + python $manage_file collectstatic --noinput + ) +fi + +# set permissions for any installed artifacts +fix-permissions /opt/app-root diff --git a/s2i/bin/run b/s2i/bin/run new file mode 100755 index 0000000..7037205 --- /dev/null +++ b/s2i/bin/run @@ -0,0 +1,103 @@ +#!/bin/bash +source /opt/app-root/etc/generate_container_user + +set -e + +function is_gunicorn_installed() { + hash gunicorn &>/dev/null +} + +function is_django_installed() { + python -c "import django" &>/dev/null +} + +function should_migrate() { + is_django_installed && [[ -z "$DISABLE_MIGRATE" ]] +} + +# Guess the number of workers according to the number of cores +function get_default_web_concurrency() { + limit_vars=$(cgroup-limits) + local $limit_vars + if [ -z "${NUMBER_OF_CORES:-}" ]; then + echo 1 + return + fi + + local max=$((NUMBER_OF_CORES*2)) + # Require at least 43 MiB and additional 40 MiB for every worker + local default=$(((${MEMORY_LIMIT_IN_BYTES:-MAX_MEMORY_LIMIT_IN_BYTES}/1024/1024 - 43) / 40)) + default=$((default > max ? max : default)) + default=$((default < 1 ? 1 : default)) + echo $default +} + +app_script_check="${APP_SCRIPT-}" +APP_SCRIPT="${APP_SCRIPT-app.sh}" +if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + exec "$APP_SCRIPT" +else + test -n "$app_script_check" && (>&2 echo "ERROR: file '$app_script_check' not found.") && exit 1 +fi + +app_file_check="${APP_FILE-}" +APP_FILE="${APP_FILE-app.py}" +if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + exec python "$APP_FILE" +else + test -n "$app_file_check" && (>&2 echo "ERROR: file '$app_file_check' not found.") && exit 1 +fi + +APP_HOME=${APP_HOME:-.} +# Look for 'manage.py' in the directory specified by APP_HOME, or the current direcotry +manage_file=$APP_HOME/manage.py + +if should_migrate; then + if [[ -f "$manage_file" ]]; then + echo "---> Migrating database ..." + python "$manage_file" migrate --noinput + else + echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." + echo "Skipped 'python manage.py migrate'." + fi +fi + +if is_gunicorn_installed; then + if [[ -z "$APP_MODULE" ]]; then + # Look only in the directory specified by APP_HOME, or the current directory + # replace all "/" with ".", remove leading "." and ".py" suffix + APP_MODULE=$(find $APP_HOME -maxdepth 1 -type f -name 'wsgi.py' | sed 's:/:.:g;s:^\.\+::;s:\.py$::') + fi + + if [[ -z "$APP_MODULE" && -f setup.py ]]; then + APP_MODULE="$(python setup.py --name)" + fi + + if [[ "$APP_MODULE" ]]; then + export WEB_CONCURRENCY=${WEB_CONCURRENCY:-$(get_default_web_concurrency)} + + echo "---> Serving application with gunicorn ($APP_MODULE) ..." + exec gunicorn "$APP_MODULE" --bind=0.0.0.0:8080 --access-logfile=- --config "$APP_CONFIG" + fi +fi + +if is_django_installed; then + if [[ -f "$manage_file" ]]; then + echo "---> Serving application with 'manage.py runserver' ..." + echo "WARNING: this is NOT a recommended way to run you application in production!" + echo "Consider using gunicorn or some other production web server." + exec python "$manage_file" runserver 0.0.0.0:8080 + else + echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." + echo "Skipped 'python manage.py runserver'." + fi +fi + +>&2 echo "ERROR: don't know how to run your application." +>&2 echo "Please set either APP_MODULE, APP_FILE or APP_SCRIPT environment variables, or create a file 'app.py' to launch your application." +exit 1 diff --git a/s2i/bin/usage b/s2i/bin/usage new file mode 100755 index 0000000..b1b09f0 --- /dev/null +++ b/s2i/bin/usage @@ -0,0 +1,18 @@ +#!/bin/sh + +DISTRO=`cat /etc/*-release | grep ^ID= | grep -Po '".*?"' | tr -d '"'` +NAMESPACE=centos +[[ $DISTRO =~ rhel* ]] && NAMESPACE=rhscl + +cat </dev/null +} + +container_exists() { + image_exists $(cat $cid_file) +} + +container_ip() { + docker inspect --format="{{ .NetworkSettings.IPAddress }}" $(cat $cid_file) +} + +run_s2i_build() { + info "Building the ${1} application image ..." + s2i build ${s2i_args} file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + exit 1 + fi + # TODO: S2I build require the application is a valid 'GIT' repository, we + # should remove this restriction in the future when a file:// is used. + info "Preparing to test ${1} ..." + pushd ${test_dir}/${1} >/dev/null + git init + git config user.email "build@localhost" && git config user.name "builder" + git add -A && git commit -m "Sample commit" + popd >/dev/null +} + +run_test_application() { + docker run --user=100001 ${CONTAINER_ARGS} --rm --cidfile=${cid_file} ${IMAGE_NAME}-testapp +} + +cleanup_app() { + info "Cleaning up app container ..." + if [ -f $cid_file ]; then + if container_exists; then + docker stop $(cat $cid_file) + fi + fi +} + +cleanup() { + info "Cleaning up the test application image" + if image_exists ${IMAGE_NAME}-testapp; then + docker rmi -f ${IMAGE_NAME}-testapp + fi + rm -rf ${test_dir}/${1}/.git +} + +check_result() { + local result="$1" + if [[ "$result" != "0" ]]; then + info "TEST FAILED (${result})" + cleanup + exit $result + fi +} + +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + local result=1 + info "Waiting for application container to start $CONTAINER_ARGS ..." + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && break + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done +} + +test_s2i_usage() { + info "Testing 's2i usage' ..." + s2i usage ${s2i_args} ${IMAGE_NAME} &>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage ..." + docker run ${IMAGE_NAME} &>/dev/null +} + +test_scl_usage() { + local run_cmd="$1" + local expected="$2" + local cid_file="$3" + + info "Testing the image SCL enable" + out=$(docker run --rm ${IMAGE_NAME} /bin/bash -c "${run_cmd}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[/bin/bash -c "${run_cmd}"] Expected '${expected}', got '${out}'" + return 1 + fi + out=$(docker exec $(cat ${cid_file}) /bin/bash -c "${run_cmd}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[exec /bin/bash -c "${run_cmd}"] Expected '${expected}', got '${out}'" + return 1 + fi + out=$(docker exec $(cat ${cid_file}) /bin/sh -ic "${run_cmd}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[exec /bin/sh -ic "${run_cmd}"] Expected '${expected}', got '${out}'" + return 1 + fi +} + +test_connection() { + info "Testing the HTTP connection (http://$(container_ip):${test_port}) ${CONTAINER_ARGS} ..." + local max_attempts=30 + local sleep_time=1 + local attempt=1 + local result=1 + while [ $attempt -le $max_attempts ]; do + response_code=$(curl -s -w %{http_code} -o /dev/null http://$(container_ip):${test_port}/) + status=$? + if [ $status -eq 0 ]; then + if [ $response_code -eq 200 ]; then + result=0 + fi + break + fi + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return $result +} + +test_application() { + local cid_file=$(mktemp -u --suffix=.cid) + # Verify that the HTTP connection can be established to test application container + run_test_application & + + # Wait for the container to write it's CID file + wait_for_cid + + test_scl_usage "python --version" "Python 3.6." "${cid_file}" + check_result $? + test_connection + check_result $? + cleanup_app +} + + +# Since we built the candidate image locally, we don't want S2I attempt to pull +# it from Docker hub +s2i_args="--force-pull=false" + +# Verify the 'usage' script is working properly when running the base image with 's2i usage ...' +test_s2i_usage +check_result $? + +# Verify the 'usage' script is working properly when running the base image with 'docker run ...' +test_docker_run_usage +check_result $? + +for app in ${WEB_APPS[@]}; do + prepare ${app} + run_s2i_build ${app} + check_result $? + + # test application with default user + test_application + + # test application with random user + CONTAINER_ARGS="-u 12345" test_application + + info "All tests for the ${app} finished successfully." + cleanup ${app} +done + +info "All tests finished successfully." diff --git a/test/setup-test-app/.gitignore b/test/setup-test-app/.gitignore new file mode 100644 index 0000000..ba74660 --- /dev/null +++ b/test/setup-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/test/setup-test-app/setup.py b/test/setup-test-app/setup.py new file mode 100644 index 0000000..e313409 --- /dev/null +++ b/test/setup-test-app/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, find_packages + + +setup ( + name = "testapp", + version = "0.1", + description = "Example application to be deployed.", + packages = find_packages(), + install_requires = ["gunicorn"], +) diff --git a/test/setup-test-app/testapp.py b/test/setup-test-app/testapp.py new file mode 100644 index 0000000..09fee1b --- /dev/null +++ b/test/setup-test-app/testapp.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/test/standalone-test-app/app.py b/test/standalone-test-app/app.py new file mode 100644 index 0000000..5ad0d39 --- /dev/null +++ b/test/standalone-test-app/app.py @@ -0,0 +1,30 @@ +import os +import time + +from gunicorn.app.base import BaseApplication +from gunicorn.six import iteritems + +print('LOADING MODULE %s' % __file__) + +def wsgi_handler(environ, start_response): + print('HANDLE REQUEST %s' % time.time()) + start_response('200 OK', [('Content-Type','text/html')]) + return [b"Hello World from standalone WSGI application!"] + +class StandaloneApplication(BaseApplication): + def __init__(self, app, options=None): + self.options = options or {} + self.application = app + super(StandaloneApplication, self).__init__() + + def load_config(self): + config = dict([(key, value) for key, value in iteritems(self.options) + if key in self.cfg.settings and value is not None]) + for key, value in iteritems(config): + self.cfg.set(key.lower(), value) + + def load(self): + return self.application + +if __name__ == '__main__': + StandaloneApplication(wsgi_handler, {'bind': ':8080'}).run() diff --git a/test/standalone-test-app/requirements.txt b/test/standalone-test-app/requirements.txt new file mode 100644 index 0000000..8f22dcc --- /dev/null +++ b/test/standalone-test-app/requirements.txt @@ -0,0 +1 @@ +gunicorn diff --git a/test/virtualenv-uwsgi-test-app/.s2i/environment b/test/virtualenv-uwsgi-test-app/.s2i/environment new file mode 100644 index 0000000..c176160 --- /dev/null +++ b/test/virtualenv-uwsgi-test-app/.s2i/environment @@ -0,0 +1 @@ +UPGRADE_PIP_TO_LATEST=1 diff --git a/test/virtualenv-uwsgi-test-app/app.sh b/test/virtualenv-uwsgi-test-app/app.sh new file mode 100755 index 0000000..397240a --- /dev/null +++ b/test/virtualenv-uwsgi-test-app/app.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# First test virtualenv environment and pip upgrade + +echo "Testing that the virtual environment's Python is being used ..." +if [ "$(which python)" != "/opt/app-root/bin/python" ]; then + echo "ERROR: Initialization of the virtual environment failed." + exit 1 +fi + +echo "Testing UPGRADE_PIP_TO_LATEST=1 (set in .s2i/environment) ..." +pip_major_version=$(pip --version | cut -d" " -f2 | cut -d"." -f1) +if [ -z "$pip_major_version" ] || [ "$pip_major_version" -lt "9" ]; then + echo "ERROR: Failed to upgrade pip to version 9 or later." + exit 1 +fi + + +# Now test the uwsgi server + +exec uwsgi \ + --http-socket :8080 \ + --die-on-term \ + --master \ + --single-interpreter \ + --enable-threads \ + --threads=5 \ + --thunder-lock \ + --module wsgi diff --git a/test/virtualenv-uwsgi-test-app/requirements.txt b/test/virtualenv-uwsgi-test-app/requirements.txt new file mode 100644 index 0000000..66d4205 --- /dev/null +++ b/test/virtualenv-uwsgi-test-app/requirements.txt @@ -0,0 +1,2 @@ +uWSGI +Flask diff --git a/test/virtualenv-uwsgi-test-app/wsgi.py b/test/virtualenv-uwsgi-test-app/wsgi.py new file mode 100644 index 0000000..96e1a61 --- /dev/null +++ b/test/virtualenv-uwsgi-test-app/wsgi.py @@ -0,0 +1,9 @@ +from flask import Flask +application = Flask(__name__) + +@application.route('/') +def hello(): + return b'Hello World from uWSGI hosted WSGI application!' + +if __name__ == '__main__': + application.run()