Initial commit

This commit is contained in:
Honza Horak 2017-08-21 00:28:57 +02:00
commit a3e6c67270
35 changed files with 1152 additions and 0 deletions

68
Dockerfile Normal file
View file

@ -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 <sclorg@redhat.com>"
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

246
README.md Normal file
View file

@ -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
------------------------
* **`<python-version>`**
* **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 <CONTAINER_ID> /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.

View file

@ -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

View file

@ -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

59
s2i/bin/assemble Executable file
View file

@ -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

103
s2i/bin/run Executable file
View file

@ -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

18
s2i/bin/usage Executable file
View file

@ -0,0 +1,18 @@
#!/bin/sh
DISTRO=`cat /etc/*-release | grep ^ID= | grep -Po '".*?"' | tr -d '"'`
NAMESPACE=centos
[[ $DISTRO =~ rhel* ]] && NAMESPACE=rhscl
cat <<EOF
This is a S2I python-3.6 ${DISTRO} base image:
To use it, install S2I: https://github.com/openshift/source-to-image
Sample invocation:
s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.6/test/setup-test-app/ ${NAMESPACE}/python-36-${DISTRO}7 python-sample-app
You can then run the resulting image via:
docker run -p 8080:8080 python-sample-app
EOF

View file

@ -0,0 +1 @@
APP_HOME=project

View file

@ -0,0 +1,3 @@
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/plain')])
return [b"Hello World from app-home WSGI application!"]

View file

@ -0,0 +1 @@
gunicorn

61
test/django-test-app/.gitignore vendored Normal file
View file

@ -0,0 +1,61 @@
# Django
db.sqlite3
staticfiles/
# 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/

10
test/django-test-app/manage.py Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View file

View file

@ -0,0 +1,103 @@
"""
Django settings for project project.
Generated by 'django-admin startproject' using Django 1.8.1.
For more information on this file, see
https://docs.djangoproject.com/en/1.8/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.8/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'y*b^6p#z&cm2)8rzgbp2i4k*+rg2h%60l*bmf6hg&ro!z0-ael'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
)
ROOT_URLCONF = 'project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.8/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

View file

@ -0,0 +1,21 @@
"""project URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.8/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Add an import: from blog import urls as blog_urls
2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls))
"""
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
]

View file

@ -0,0 +1,16 @@
"""
WSGI config for project project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
application = get_wsgi_application()

View file

@ -0,0 +1 @@
Django==1.8.1

View file

@ -0,0 +1,2 @@
Flask
gunicorn

View file

@ -0,0 +1,41 @@
import locale
import os
import sys
from flask import Flask
application = Flask(__name__)
@application.route('/')
def hello():
assert os.environ['PYTHONIOENCODING'] == 'UTF-8'
assert os.environ['LC_ALL'] == 'en_US.UTF-8'
assert os.environ['LANG'] == 'en_US.UTF-8'
assert locale.getdefaultlocale() == ('en_US', 'UTF-8')
assert locale.getpreferredencoding() == 'UTF-8'
print(u'\u292e')
return b'Hello World from locale test application!'
print('-- GLOBAL --')
for k,v in os.environ.items():
print('%r=%r' % (k, v))
print()
print(sys.stdout.encoding)
print(locale.getlocale())
print(locale.getdefaultlocale())
print(locale.getpreferredencoding())
try:
print(u'\u292e')
except Exception as e:
print(e)
print('------------')
if __name__ == '__main__':
application.run()

View file

@ -0,0 +1,14 @@
import os
import mod_wsgi.server
mod_wsgi.server.start(
'--log-to-terminal',
'--port', '8080',
'--trust-proxy-header', 'X-Forwarded-For',
'--trust-proxy-header', 'X-Forwarded-Port',
'--trust-proxy-header', 'X-Forwarded-Proto',
'--processes', os.environ.get('MOD_WSGI_PROCESSES', '1'),
'--threads', os.environ.get('MOD_WSGI_THREADS', '5'),
'--application-type', 'module',
'--entry-point', 'wsgi'
)

View file

@ -0,0 +1,2 @@
mod_wsgi
Flask

View file

@ -0,0 +1,9 @@
from flask import Flask
application = Flask(__name__)
@application.route('/')
def hello():
return b'Hello World from mod_wsgi hosted WSGI application!'
if __name__ == '__main__':
application.run()

View file

@ -0,0 +1,2 @@
gunicorn
numpy

View file

@ -0,0 +1,7 @@
import numpy
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/plain')])
matrix = numpy.array([[1,2,3],[6,5,4],[7,8,8]])
matrix.dot(numpy.linalg.inv(matrix))
return [b"Hello World from numpy WSGI application!"]

196
test/run Executable file
View file

@ -0,0 +1,196 @@
#!/bin/bash
#
# The 'run' performs a simple test that verifies that S2I image.
# The main focus here is to excersise the S2I scripts.
#
# IMAGE_NAME specifies a name of the candidate image used for testing.
# The image has to be available before this script is executed.
#
IMAGE_NAME=${IMAGE_NAME:-openshift/python-36-centos7-candidate}
declare -a WEB_APPS=({standalone,setup,django,numpy,app-home,virtualenv-uwsgi,locale,mod-wsgi}-test-app)
# TODO: Make command compatible for Mac users
test_dir="$(readlink -zf $(dirname "${BASH_SOURCE[0]}"))"
image_dir=$(readlink -zf ${test_dir}/..)
# TODO: This should be part of the image metadata
test_port=8080
info() {
echo -e "\n\e[1m[INFO] $@\e[0m\n"
}
image_exists() {
docker inspect $1 &>/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."

57
test/setup-test-app/.gitignore vendored Normal file
View file

@ -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/

View file

@ -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"],
)

View file

@ -0,0 +1,4 @@
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/plain')])
return [b"Hello from gunicorn WSGI application!"]

View file

@ -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()

View file

@ -0,0 +1 @@
gunicorn

View file

@ -0,0 +1 @@
UPGRADE_PIP_TO_LATEST=1

View file

@ -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

View file

@ -0,0 +1,2 @@
uWSGI
Flask

View file

@ -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()