diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..39459aba --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +--- + +version: 2 +updates: + - package-ecosystem: gitsubmodule + directory: / + schedule: + interval: weekly + labels: + - 'ready for review' + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + labels: + - 'ready for review' diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml new file mode 100644 index 00000000..7cdbfca4 --- /dev/null +++ b/.github/workflows/build-and-push.yml @@ -0,0 +1,158 @@ +name: Build and push container images to Quay.io registry +on: + push: + branches: + - master + schedule: + - cron: '0 1 * * 3' + +jobs: + build-and-push: + if: github.repository_owner == 'sclorg' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - dockerfile: "3.9/Dockerfile.c9s" + docker_context: "3.9" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c9s" + image_name: "python-39-c9s" + + - dockerfile: "3.9-minimal/Dockerfile.c9s" + docker_context: "3.9-minimal" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c9s" + image_name: "python-39-minimal-c9s" + + - dockerfile: "3.12-minimal/Dockerfile.rhel8" + docker_context: "3.12-minimal" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "el8" + image_name: "python-312-minimal-el8" + + - dockerfile: "3.12-minimal/Dockerfile.c9s" + docker_context: "3.12-minimal" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c9s" + image_name: "python-312-minimal-c9s" + + - dockerfile: "3.12-minimal/Dockerfile.c10s" + docker_context: "3.12-minimal" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c10s" + image_name: "python-312-minimal-c10s" + + - dockerfile: "3.12/Dockerfile.c9s" + docker_context: "3.12" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c9s" + image_name: "python-312-c9s" + + - dockerfile: "3.12/Dockerfile.c10s" + docker_context: "3.12" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c10s" + image_name: "python-312-c10s" + + - dockerfile: "3.13/Dockerfile.fedora" + docker_context: "3.13" + registry_namespace: "fedora" + quayio_username: "QUAY_IMAGE_FEDORA_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_FEDORA_BUILDER_TOKEN" + tag: "313" + image_name: "python-313" + + - dockerfile: "3.13-minimal/Dockerfile.fedora" + docker_context: "3.13-minimal" + registry_namespace: "fedora" + quayio_username: "QUAY_IMAGE_FEDORA_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_FEDORA_BUILDER_TOKEN" + tag: "313" + image_name: "python-313-minimal" + + - dockerfile: "3.13/Dockerfile.c10s" + docker_context: "3.13" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c10s" + image_name: "python-313-c10s" + + - dockerfile: "3.13-minimal/Dockerfile.c10s" + docker_context: "3.13-minimal" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c10s" + image_name: "python-313-minimal-c10s" + + - dockerfile: "3.14/Dockerfile.fedora" + docker_context: "3.14" + registry_namespace: "fedora" + quayio_username: "QUAY_IMAGE_FEDORA_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_FEDORA_BUILDER_TOKEN" + tag: "314" + image_name: "python-314" + + - dockerfile: "3.14-minimal/Dockerfile.fedora" + docker_context: "3.14-minimal" + registry_namespace: "fedora" + quayio_username: "QUAY_IMAGE_FEDORA_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_FEDORA_BUILDER_TOKEN" + tag: "314" + image_name: "python-314-minimal" + + - dockerfile: "3.14/Dockerfile.c9s" + docker_context: "3.14" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c9s" + image_name: "python-314-c9s" + + - dockerfile: "3.14-minimal/Dockerfile.c9s" + docker_context: "3.14-minimal" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c9s" + image_name: "python-314-minimal-c9s" + + - dockerfile: "3.14-minimal/Dockerfile.c10s" + docker_context: "3.14-minimal" + registry_namespace: "sclorg" + quayio_username: "QUAY_IMAGE_SCLORG_BUILDER_USERNAME" + quayio_token: "QUAY_IMAGE_SCLORG_BUILDER_TOKEN" + tag: "c10s" + image_name: "python-314-minimal-c10s" + + steps: + - name: Build and push to quay.io registry + uses: sclorg/build-and-push-action@v4 + with: + registry: "quay.io" + registry_namespace: ${{ matrix.registry_namespace }} + registry_username: ${{ secrets[matrix.quayio_username] }} + registry_token: ${{ secrets[matrix.quayio_token] }} + dockerfile: ${{ matrix.dockerfile }} + tag: ${{ matrix.tag }} + image_name: ${{ matrix.image_name }} + archs: amd64, arm64, ppc64le + readme: "${{ matrix.docker_context }}/README.md" + quay_application_token: ${{ secrets.QUAY_IMAGE_SCLORG_UPDATE_DESC }} diff --git a/.github/workflows/container-tests.yml b/.github/workflows/container-tests.yml new file mode 100644 index 00000000..67904ab7 --- /dev/null +++ b/.github/workflows/container-tests.yml @@ -0,0 +1,19 @@ +--- + +on: + issue_comment: + types: + - created +jobs: + distgen-check: + uses: "sclorg/ci-actions/.github/workflows/distgen-check.yml@main" + check-readme: + uses: "sclorg/ci-actions/.github/workflows/check-readme.yml@main" + container-tests: + needs: check-readme + uses: "sclorg/ci-actions/.github/workflows/container-tests.yml@main" + with: + enabled-tests: '["container","container-pytest","openshift-pytest"]' + versions: '[ "3.9", "3.9-minimal", "3.12", "3.12-minimal", "3.13", "3.13-minimal", "3.14", "3.14-minimal" ]' + openshift-versions: '[ "3.8", "3.9", "3.12", "3.12-minimal" ]' + secrets: inherit diff --git a/.gitignore b/.gitignore index 294bbb8e..65c7ac03 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ openshift.local.* auto_targets.mk .image-id .image-id.raw +.image-id-from # help.1 files complied by container-common-scripts from README.md files */root/help.1 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 19ade569..00000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -# The main project repository https://github.com/sclorg/s2i-python-container.git -# uses CentOS-CI, Travis is not enabled at this moment. -# The purpose of this file is to add possibility of an alternative CI service -# in repository forks. - -sudo: required - -services: - - docker - -language: go - -go: latest - -before_install: - - sudo apt-get update -qq - - sudo apt-get install -y go-md2man - -install: - - go get github.com/openshift/source-to-image - - pushd ${GOPATH}/src/github.com/openshift/source-to-image - - export PATH=$PATH:${GOPATH}/src/github.com/openshift/source-to-image/_output/local/bin/linux/amd64/ - - hack/build-go.sh - - popd - - pip install --user distgen - -script: DG=dg SKIP_SQUASH=1 make test TARGET=centos diff --git a/2.7/Dockerfile b/2.7/Dockerfile deleted file mode 100644 index fec441fe..00000000 --- a/2.7/Dockerfile +++ /dev/null @@ -1,69 +0,0 @@ -# This image provides a Python 2.7 environment you can use to run your Python -# applications. -FROM centos/s2i-base-centos7 - -EXPOSE 8080 - -ENV PYTHON_VERSION=2.7 \ - 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 SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ - DESCRIPTION="Python $PYTHON_VERSION available as 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 2.7" \ - io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python27,python-27,rh-python27" \ - com.redhat.component="python27-container" \ - name="centos/python-27-centos7" \ - version="2.7" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=2.7/test/setup-test-app/ centos/python-27-centos7 python-sample-app" \ - maintainer="SoftwareCollections.org " - -RUN INSTALL_PKGS="libjpeg-turbo libjpeg-turbo-devel python27 python27-python-devel python27-python-setuptools \ - python27-python-pip nss_wrapper httpd24 httpd24-httpd-devel httpd24-mod_ssl \ - httpd24-mod_auth_kerb httpd24-mod_ldap httpd24-mod_session atlas-devel gcc-gfortran \ - libffi-devel libtool-ltdl enchant" && \ - yum install -y centos-release-scl && \ - yum -y --setopt=tsflags=nodocs install --enablerepo=centosplus $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - # Remove centos-logos (httpd dependency) to keep image size smaller. - rpm -e --nodeps centos-logos && \ - yum -y clean all --enablerepo='*' - -# 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. -# - 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 source scl_source enable python27 && \ - virtualenv ${APP_ROOT} && \ - chown -R 1001:0 ${APP_ROOT} && \ - fix-permissions ${APP_ROOT} -P && \ - rpm-file-permissions - -USER 1001 - -# Set the default CMD to print the usage of the language image. -CMD $STI_SCRIPTS_PATH/usage diff --git a/2.7/Dockerfile.rhel7 b/2.7/Dockerfile.rhel7 deleted file mode 100644 index 8587e954..00000000 --- a/2.7/Dockerfile.rhel7 +++ /dev/null @@ -1,70 +0,0 @@ -# This image provides a Python 2.7 environment you can use to run your Python -# applications. -FROM rhscl/s2i-base-rhel7 - -EXPOSE 8080 - -ENV PYTHON_VERSION=2.7 \ - 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 SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ - DESCRIPTION="Python $PYTHON_VERSION available as 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 2.7" \ - io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python27,python-27,rh-python27" \ - com.redhat.component="python27-container" \ - name="rhscl/python-27-rhel7" \ - version="2.7" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=2.7/test/setup-test-app/ rhscl/python-27-rhel7 python-sample-app" \ - com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ - maintainer="SoftwareCollections.org " - -RUN INSTALL_PKGS="python27 python27-python-devel python27-python-setuptools python27-python-pip nss_wrapper \ - httpd24 httpd24-httpd-devel httpd24-mod_ssl httpd24-mod_auth_kerb httpd24-mod_ldap \ - httpd24-mod_session atlas-devel gcc-gfortran libffi-devel libtool-ltdl enchant" && \ - yum install -y yum-utils && \ - prepare-yum-repositories rhel-server-rhscl-7-rpms && \ - yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - # Remove redhat-logos (httpd dependency) to keep image size smaller. - rpm -e --nodeps redhat-logos && \ - yum -y clean all --enablerepo='*' - -# 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. -# - 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 source scl_source enable python27 && \ - virtualenv ${APP_ROOT} && \ - chown -R 1001:0 ${APP_ROOT} && \ - fix-permissions ${APP_ROOT} -P && \ - rpm-file-permissions - -USER 1001 - -# Set the default CMD to print the usage of the language image. -CMD $STI_SCRIPTS_PATH/usage diff --git a/2.7/Dockerfile.rhel8 b/2.7/Dockerfile.rhel8 deleted file mode 100644 index 49385d3d..00000000 --- a/2.7/Dockerfile.rhel8 +++ /dev/null @@ -1,77 +0,0 @@ -# This image provides a Python 2.7 environment you can use to run your Python -# applications. -FROM ubi8/s2i-base:1 - -EXPOSE 8080 - -ENV PYTHON_VERSION=2.7 \ - 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 - -# RHEL7 base images atomatically set these envvars to run scl_enable. RHEl8 -# images, however, don't as most images don't need SCLs any more. But we want -# to run it even on RHEL8, because we set the virtualenv environment as part of -# that script -ENV BASH_ENV=${APP_ROOT}/etc/scl_enable \ - ENV=${APP_ROOT}/etc/scl_enable \ - PROMPT_COMMAND=". ${APP_ROOT}/etc/scl_enable" - -ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ - DESCRIPTION="Python $PYTHON_VERSION available as 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 2.7" \ - io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python27,python-27,rh-python27" \ - com.redhat.component="python-27-container" \ - name="ubi8/python-27" \ - version="2.7" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=2.7/test/setup-test-app/ ubi8/python-27 python-sample-app" \ - com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ - maintainer="SoftwareCollections.org " - -RUN INSTALL_PKGS="python2 python2-devel glibc-langpack-en python2-virtualenv python2-setuptools \ - python2-pip nss_wrapper httpd httpd-devel mod_ssl \ - mod_auth_gssapi mod_ldap mod_session atlas-devel gcc-gfortran \ - libffi-devel libtool-ltdl enchant" && \ - yum -y module enable python27:2.7 httpd:2.4 && \ - yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - # Remove redhat-logos-httpd (httpd dependency) to keep image size smaller. - rpm -e --nodeps redhat-logos-httpd && \ - yum -y clean all --enablerepo='*' - -# 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. -# - 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 \ - virtualenv-$PYTHON_VERSION ${APP_ROOT} && \ - chown -R 1001:0 ${APP_ROOT} && \ - fix-permissions ${APP_ROOT} -P && \ - rpm-file-permissions - -USER 1001 - -# Set the default CMD to print the usage of the language image. -CMD $STI_SCRIPTS_PATH/usage diff --git a/2.7/cccp.yml b/2.7/cccp.yml deleted file mode 100644 index acd096a2..00000000 --- a/2.7/cccp.yml +++ /dev/null @@ -1,3 +0,0 @@ -# This is for the purpose of building this container -# on the centos container pipeline. -job-id: python-27-centos7 diff --git a/2.7/root/opt/app-root/etc/scl_enable b/2.7/root/opt/app-root/etc/scl_enable deleted file mode 100644 index a1185ff6..00000000 --- a/2.7/root/opt/app-root/etc/scl_enable +++ /dev/null @@ -1,9 +0,0 @@ -# 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 -if head "/etc/redhat-release" | grep -q "^CentOS Linux release 7" || \ - head "/etc/redhat-release" | grep -q "^Red Hat Enterprise Linux\( Server\)\? release 7"; then - source scl_source enable httpd24 $NODEJS_SCL python27 -fi -source /opt/app-root/bin/activate diff --git a/2.7/s2i/bin/usage b/2.7/s2i/bin/usage deleted file mode 100755 index 9548acdd..00000000 --- a/2.7/s2i/bin/usage +++ /dev/null @@ -1,18 +0,0 @@ -#!/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 ..." - ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} -} - -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() { - # positive test & non-zero exit status = ERROR - # negative test & zero exit status = ERROR - local result="$1" - local type=${2:-positive} # if not defined, we expect possitive test type (zero exit code) - if [[ "$type" == "positive" && "$result" != "0" ]]; then - info "TEST FAILED (${type}), EXPECTED:0 GOT:${result}" - cleanup - exit $result - elif [[ "$type" == "negative" && "$result" == "0" ]]; then - info "TEST FAILED (${type}), EXPECTED: non-zero GOT:${result}" - cleanup - exit 1 - fi -} - -wait_for_cid() { - local max_attempts=10 - local sleep_time=1 - local attempt=1 - info "Waiting for application container to start $CONTAINER_ARGS ..." - while [ $attempt -le $max_attempts ]; do - [ -f $cid_file ] && [ -s $cid_file ] && return 0 - attempt=$(( $attempt + 1 )) - sleep $sleep_time - done - return 1 -} - -test_s2i_usage() { - info "Testing 's2i usage' ..." - ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/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 - # Some test apps have tests in their startup code so we have to check - # that the container starts at all - check_result $? - - test_scl_usage "python --version" "Python $VERSION." "${cid_file}" - check_result $? - test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? - test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? - test_connection - check_result $? - cleanup_app -} - -test_latest_imagestreams() { - local result=1 - info "Testing the latest version in imagestreams" - # Switch to root directory of a container - pushd "${test_dir}/../.." >/dev/null || return 1 - ct_check_latest_imagestreams - result=$? - popd >/dev/null || return 1 - check_result $result -} - -# Since we built the candidate image locally, we don't want S2I attempt to pull -# it from Docker hub -s2i_args="--pull-policy=never" - -# 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 debugging purposes, this script can be run with one or more arguments -# those arguments list is a sub-set of values in the WEB_APPS array defined above -# Example: ./run app-home-test-app pipenv-test-app -for app in ${@:-${WEB_APPS[@]}}; do - prepare ${app} - run_s2i_build ${app} - RESULT=$? - if [[ "$app" == *"-should-fail-"* ]]; then - # Tests with '-should-fail-' in their name should fail during a build, expecting non-zero exit status - check_result $RESULT "negative" - continue - else - check_result $RESULT - fi - - # test application with default user - test_application - - # test application with random user - CONTAINER_ARGS="--user 12345" test_application - - # test application with init wrapper - CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application - - test_latest_imagestreams - - info "All tests for the ${app} finished successfully." - cleanup ${app} -done - -info "All tests finished successfully." diff --git a/2.7/test/run-openshift b/2.7/test/run-openshift deleted file mode 120000 index ec13e4a1..00000000 --- a/2.7/test/run-openshift +++ /dev/null @@ -1 +0,0 @@ -../../test/run-openshift \ No newline at end of file diff --git a/2.7/test/test-lib-openshift.sh b/2.7/test/test-lib-openshift.sh deleted file mode 120000 index 4f9f2996..00000000 --- a/2.7/test/test-lib-openshift.sh +++ /dev/null @@ -1 +0,0 @@ -../../common/test-lib-openshift.sh \ No newline at end of file diff --git a/2.7/test/test-lib-python.sh b/2.7/test/test-lib-python.sh deleted file mode 120000 index 0fa72132..00000000 --- a/2.7/test/test-lib-python.sh +++ /dev/null @@ -1 +0,0 @@ -../../test/test-lib-python.sh \ No newline at end of file diff --git a/3.12-minimal/Dockerfile.c10s b/3.12-minimal/Dockerfile.c10s new file mode 100644 index 00000000..d66575c8 --- /dev/null +++ b/3.12-minimal/Dockerfile.c10s @@ -0,0 +1,104 @@ +FROM quay.io/centos/centos:stream10-minimal + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.12 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi10-python-312 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el10" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.12" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python312,python-312,rh-python312" \ + com.redhat.component="python-312-container" \ + name="sclorg/python-312-minimal-c10s" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.12-minimal/test/setup-test-app/ ubi10/python-312-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi10-python-312-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.12 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.12-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.12-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.12-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.12 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.12-minimal/Dockerfile.c9s b/3.12-minimal/Dockerfile.c9s new file mode 100644 index 00000000..bb4cd2ae --- /dev/null +++ b/3.12-minimal/Dockerfile.c9s @@ -0,0 +1,104 @@ +FROM quay.io/centos/centos:stream9-minimal + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.12 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi9-python-312 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el9" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.12" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python312,python-312,rh-python312" \ + com.redhat.component="python-312-container" \ + name="sclorg/python-312-minimal-c9s" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.12-minimal/test/setup-test-app/ ubi9/python-312-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi9-python-312-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.12 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.12-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.12-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.12-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.12 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.12-minimal/Dockerfile.rhel10 b/3.12-minimal/Dockerfile.rhel10 new file mode 100644 index 00000000..d133a9d4 --- /dev/null +++ b/3.12-minimal/Dockerfile.rhel10 @@ -0,0 +1,104 @@ +FROM ubi10-minimal:latest + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.12 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi10-python-312 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el10" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.12" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python312,python-312,rh-python312" \ + com.redhat.component="python-312-container" \ + name="ubi10/python-312-minimal" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.12-minimal/test/setup-test-app/ ubi10/python-312-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi10-python-312-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.12 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.12-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.12-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.12-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.12 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.12-minimal/Dockerfile.rhel8 b/3.12-minimal/Dockerfile.rhel8 new file mode 100644 index 00000000..521afbb9 --- /dev/null +++ b/3.12-minimal/Dockerfile.rhel8 @@ -0,0 +1,104 @@ +FROM ubi8/ubi-minimal:latest + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.12 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi8-python-312 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el8" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.12" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python312,python-312,rh-python312" \ + com.redhat.component="python-312-container" \ + name="ubi8/python-312-minimal" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.12-minimal/test/setup-test-app/ ubi8/python-312-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi8-python-312-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.12 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.12-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.12-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.12-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.12 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.12-minimal/Dockerfile.rhel9 b/3.12-minimal/Dockerfile.rhel9 new file mode 100644 index 00000000..86b7165f --- /dev/null +++ b/3.12-minimal/Dockerfile.rhel9 @@ -0,0 +1,104 @@ +FROM ubi9/ubi-minimal:latest + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.12 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi9-python-312 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el9" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.12" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python312,python-312,rh-python312" \ + com.redhat.component="python-312-container" \ + name="ubi9/python-312-minimal" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.12-minimal/test/setup-test-app/ ubi9/python-312-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi9-python-312-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.12 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.12-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.12-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.12-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.12 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.12-minimal/README.md b/3.12-minimal/README.md new file mode 100644 index 00000000..ef094dbc --- /dev/null +++ b/3.12-minimal/README.md @@ -0,0 +1,125 @@ +Python 3.12 container image - minimal version +============================================ + +This container image is a special version of the [full Python 3.12 container image](https://github.com/sclorg/s2i-python-container/tree/master/3.12) +provided as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.12 applications. + +Because the minimal and full images work similarly, we document here only the differences and limitations +of the minimal container image. For the documentation of common features see the [full container image docs](https://github.com/sclorg/s2i-python-container/tree/master/3.12). + +The Python 3.12 minimal container image is currently considered a tech-preview and only available on quay.io. +The image is built on top of the [official CentOS Stream base containers](quay.io/centos/centos). + +To pull the Python 3.12 minimal container image to build on, run + +``` +podman pull quay.io/sclorg/python-312-minimal-c9s +``` + +Description +----------- + +The full container image is a universal base image to build your containerized applications on top of. However, its universal nature +means that the resulting containers it produces consume a lot of disk space. This is caused mainly by the fact that the image contains +npm, compilers, header files and some other packages one might need to install and deploy their applications. + +Because size does matter for us and our customers, we have prepared this minimal container image with very limited subset +of installed packages. There are no compilers, no header files, no npm etc and the yum package manager is replaced with a minimalistic +reimplementation called microdnf, making the resulting container images much smaller. This creates some limitations +but we provide ways to workaround them. + +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Limitations +----------- + +1. There is only a very limited subset of packages installed. They are choosen carefully to satisfy most of the Python apps but your app might have some special needs. +1. There is no npm and nodejs. +1. There are no compilers and header files. Installation from Python wheels should still work but compilation from a source code is not supported out of the box. + +In the next chapter, we provide three possible workarounds for the mentioned limitations of the minimal container image. + +Possible solutions for the limitations +-------------------------------------- + +### Use the full container image + +It's easy at that. If you don't want to write your own Dockerfile and disk space is not a problem, use +the full universal container image and you should be fine. + +### Build your own container image on top of the minimal container image + +Let's say that your application depends on uwsgi. uwsgi cannot be installed from Python wheel and has to be +compiled from source which requires some additional packages to be installed - namely gcc for the compilation +itself and python3.12-devel containing Python header files. + +To solve that problem, you can use all the pieces provided by the minimal container image and just add one more +step to install the missing dependencies: + +``` +FROM python-312-minimal + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src + +# Install packages necessary for compiling uwsgi from source +RUN microdnf install -y gcc python3.12-devel +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you do it this way, your problem with the missing packages is solved. But there is also one disadvantage: the resulting +runtime image contains unnecessary compiler and Python header files. How to solve this? Uninstalling them at the end +of the Dockerfile is not really a solution but we have one. Keep reading. + +### Build on full image, run on minimal image + +Did you know that you can copy files from one image to another one during a build? That's the feature we are gonna use now. +We use the full container image with all compilers and other usefull packages installed to build our app and its dependencies +and we then move the result including the whole virtual environemnt to the minimal container image. + +This app needs mod_wsgi and to install (compile it from source) it, we'll need: httpd-devel for header files, gcc and redhat-rpm-config +as a compiler and configuratuion and finally python3.12-devel containing Python header files. There is no need to install those packages +manually because the full container image already contains them. However, the application needs httpd as a runtime dependency +so we need to install it to the minimal container image as well. + +``` +# Part 1 - build + +FROM python-312 as builder + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the application's dependencies from PyPI +RUN /usr/libexec/s2i/assemble + +# Part 2 - deploy + +FROM python-312-minimal + +# Copy app sources together with the whole virtual environment from the builder image +COPY --from=builder $APP_ROOT $APP_ROOT + +# Install httpd package - runtime dependency of our application +USER 0 +RUN microdnf install -y httpd +USER 1001 + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +This way, the resulting container image does contain only necessary dependencies and it's much lighter. diff --git a/3.5/root/opt/app-root/etc/generate_container_user b/3.12-minimal/root/opt/app-root/etc/generate_container_user similarity index 90% rename from 3.5/root/opt/app-root/etc/generate_container_user rename to 3.12-minimal/root/opt/app-root/etc/generate_container_user index 43ee7ecb..a7fd74d6 100644 --- a/3.5/root/opt/app-root/etc/generate_container_user +++ b/3.12-minimal/root/opt/app-root/etc/generate_container_user @@ -9,7 +9,7 @@ if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"1001" ]; then 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 + echo "default:x:${USER_ID}:${GROUP_ID}:Default Application User:${HOME}:/bin/bash" >> $NSS_WRAPPER_PASSWD export NSS_WRAPPER_PASSWD export NSS_WRAPPER_GROUP diff --git a/3.12-minimal/root/opt/wheels b/3.12-minimal/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.12-minimal/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.12-minimal/root/usr/bin/cgroup-limits b/3.12-minimal/root/usr/bin/cgroup-limits new file mode 100755 index 00000000..6fa19153 --- /dev/null +++ b/3.12-minimal/root/usr/bin/cgroup-limits @@ -0,0 +1,192 @@ +#!/usr/libexec/platform-python + +""" +Script for parsing cgroup information + +This script will read some limits from the cgroup system and parse +them, printing out "VARIABLE=VALUE" on each line for every limit that is +successfully read. Output of this script can be directly fed into +bash's export command. Recommended usage from a bash script: + + set -o errexit + export_vars=$(cgroup-limits) ; export $export_vars + +Variables currently supported: + MAX_MEMORY_LIMIT_IN_BYTES + Maximum possible limit MEMORY_LIMIT_IN_BYTES can have. This is + currently constant value of 9223372036854775807. + MEMORY_LIMIT_IN_BYTES + Maximum amount of user memory in bytes. If this value is set + to the same value as MAX_MEMORY_LIMIT_IN_BYTES, it means that + there is no limit set. The value is taken from + /sys/fs/cgroup/memory/memory.limit_in_bytes for cgroups v1 + and from /sys/fs/cgroup/memory.max for cgroups v2 + NUMBER_OF_CORES + Number of CPU cores that can be used. If both the cpu and cpuset + controllers specify a limit, the controller with the lowest CPU + limit takes precedence. For the cpu controller, the value is + calculated from /sys/fs/cgroup/cpu/cpu.cfs_{quota,period}_us for + cgroups v1 and from /sys/fs/cgroup/cpuset.cpus.effective for cgroups v2. + For the cpuset controller, the value is taken from + /sys/fs/cgroup/cpuset/cpuset.cpus for cgroups v1 and from + /sys/fs/cgroup/cpuset.cpus.effective for cgroups v2 + NO_MEMORY_LIMIT + Set to "true" if MEMORY_LIMIT_IN_BYTES is so high that the caller + can act as if no memory limit was set. Undefined otherwise. + +Note about non-root containers: + + Per podman-run(1) man page, on some systems, changing the resource limits + may not be allowed for non-root users. For more details, see + https://github.com/containers/podman/blob/main/troubleshooting.md#26-running-containers-with-resource-limits-fails-with-a-permissions-error. + +How to test this script: + + Run a container as root and see whether the output of available memory + and CPUs match what is either available on the host or specified via + cgroups limits by the container runtime (podman). + + For example: + + # This should return NO_MEMORY_LIMIT=true, NUMBER_OF_CORES= + sudo podman run -ti --rm quay.io/fedora/s2i-core /usr/bin/cgroup-limits + + # This should return MEMORY_LIMIT_IN_BYTES=2147483648, NUMBER_OF_CORES=3 + # 3 CPUs despite --cpus 4 was given is correct, because the cpuset 2-4 means + # running on 3 concrete processors only, which is lower limit than --cpus + # and thus is preferred + sudo podman run -ti --rm --cpus 4 --cpuset-cpus=2-4 --memory 2G quay.io/fedora/s2i-core /usr/bin/cgroup-limits +""" + +from __future__ import division +from __future__ import print_function +import sys +import subprocess + + +def _read_file(path): + try: + with open(path, 'r') as f: + return f.read().strip() + except IOError: + return None + + +def get_memory_limit(): + """ + Read memory limit, in bytes. + """ + + limit = _read_file('/sys/fs/cgroup/memory/memory.limit_in_bytes') + # If first file does not exist, try cgroups v2 file + limit = limit or _read_file('/sys/fs/cgroup/memory.max') + if limit is None or not limit.isdigit(): + if limit == 'max': + return 9223372036854775807 + print("Warning: Can't detect memory limit from cgroups", + file=sys.stderr) + return None + return int(limit) + + +def get_number_of_cores(): + """ + Read number of CPU cores. + """ + + limits = [l for l in [_read_cpu_quota(), _read_cpuset_size(), _read_nproc()] if l] + if not limits: + return None + return min(limits) + + +def _read_cpu_quota(): + + quota, period = None, None + + quota = _read_file("/sys/fs/cgroup/cpu/cpu.cfs_quota_us") + if quota: + # cgroups v1 + quota = quota.strip() + if quota == "-1": + return None + + period = _read_file("/sys/fs/cgroup/cpu/cpu.cfs_period_us") + if period: + period = period.strip() + + else: + # cgroups v2 + line = _read_file("/sys/fs/cgroup/cpu.max") + if line: + fields = line.split() + + if len(fields) >= 2: + quota = fields[0] + if quota == "max": + return None + period = fields[1] + + + if quota and quota.isdigit() and period and period.isdigit(): + return int(quota)//int(period) + + print("Warning: Can't detect cpu quota from cgroups", + file=sys.stderr) + return None + + +def _read_cpuset_size(): + + core_count = 0 + + line = _read_file('/sys/fs/cgroup/cpuset/cpuset.cpus') + # If first file does not exist, try cgroups v2 file + line = line or _read_file('/sys/fs/cgroup/cpuset.cpus.effective') + if line is None: + # None of the files above exists when running podman as non-root, + # so in that case, this warning is printed every-time + print("Warning: Can't detect cpuset size from cgroups, will use nproc", + file=sys.stderr) + return None + + for group in line.split(','): + core_ids = list(map(int, group.split('-'))) + if len(core_ids) == 2: + core_count += core_ids[1] - core_ids[0] + 1 + else: + core_count += 1 + + return core_count + + +def _read_nproc(): + """ + Returns number of cores without looking at cgroup limits. + This might work as the last resort when running a container as non-root + where cgroups v2 resource limits cannot be set without a specific + configuration (per podman-run(1) man page). + """ + try: + stdout, stderr = subprocess.Popen('nproc', stdout=subprocess.PIPE).communicate() + return int(stdout) + except EnvironmentError as e: + if e.errno != errno.ENOENT: + raise + return None + + +if __name__ == "__main__": + env_vars = { + "MAX_MEMORY_LIMIT_IN_BYTES": 9223372036854775807, + "MEMORY_LIMIT_IN_BYTES": get_memory_limit(), + "NUMBER_OF_CORES": get_number_of_cores() + } + + env_vars = {k: v for k, v in env_vars.items() if v is not None} + + if env_vars.get("MEMORY_LIMIT_IN_BYTES", 0) >= 92233720368547: + env_vars["NO_MEMORY_LIMIT"] = "true" + + for key, value in env_vars.items(): + print("{0}={1}".format(key, value)) diff --git a/3.12-minimal/root/usr/bin/fix-permissions b/3.12-minimal/root/usr/bin/fix-permissions new file mode 100755 index 00000000..ddd33ace --- /dev/null +++ b/3.12-minimal/root/usr/bin/fix-permissions @@ -0,0 +1,27 @@ +#!/bin/sh + +# Allow this script to fail without failing a build +set +e + +SYMLINK_OPT=${2:--L} + +# Fix permissions on the given directory or file to allow group read/write of +# regular files and execute of directories. + +[ $(id -u) -ne 0 ] && CHECK_OWNER=" -uid $(id -u)" + +# If argument does not exist, script will still exit with 0, +# but at least we'll see something went wrong in the log +if ! [ -e "$1" ] ; then + echo "ERROR: File or directory $1 does not exist." >&2 + # We still want to end successfully + exit 0 +fi + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} \! -gid 0 -exec chgrp 0 {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} \! -perm -g+rw -exec chmod g+rw {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} -perm /u+x -a \! -perm /g+x -exec chmod g+x {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} -type d \! -perm /g+x -exec chmod g+x {} + + +# Always end successfully +exit 0 diff --git a/3.12-minimal/root/usr/bin/rpm-file-permissions b/3.12-minimal/root/usr/bin/rpm-file-permissions new file mode 100755 index 00000000..8be1fb0d --- /dev/null +++ b/3.12-minimal/root/usr/bin/rpm-file-permissions @@ -0,0 +1,21 @@ +#!/bin/sh + +CHECK_DIRS="/ /opt /etc /usr /usr/bin /usr/lib /usr/lib64 /usr/share /usr/libexec" + +rpm_format="[%{FILESTATES:fstate} %7{FILEMODES:octal} %{FILENAMES:shescape}\n]" + +rpm -q --qf "$rpm_format" filesystem | while read line +do + eval "set -- $line" + + case $1 in + normal) ;; + *) continue ;; + esac + + case " $CHECK_DIRS " in + *" $3 "*) + chmod "${2: -4}" "$3" + ;; + esac +done diff --git a/3.7/s2i/bin/assemble b/3.12-minimal/s2i/bin/assemble similarity index 82% rename from 3.7/s2i/bin/assemble rename to 3.12-minimal/s2i/bin/assemble index 3aaf9cc2..34a68dbb 100755 --- a/3.7/s2i/bin/assemble +++ b/3.12-minimal/s2i/bin/assemble @@ -11,7 +11,7 @@ function should_collectstatic() { function virtualenv_bin() { # New versions of Python (>3.6) should use venv module # from stdlib instead of virtualenv package - python3.7 -m venv $1 + python3.12 -m venv $1 } # Install pipenv or micropipenv to the separate virtualenv to isolate it @@ -53,20 +53,17 @@ mv /tmp/src/* "$HOME" # set permissions for any installed artifacts fix-permissions /opt/app-root -P -# We have to first upgrade pip to at least 19.3 because: -# * pip < 9 does not support different packages' versions for Python 2/3 -# * pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 wheels -# support platforms like ppc64le, aarch64 or armv7 -echo "---> Upgrading pip to version 19.3.1 ..." -if ! pip install -U "pip==19.3.1"; then - echo "WARNING: Installation of 'pip==19.3.1' failed, trying again from official PyPI with pip --isolated install" - pip install --isolated -U "pip==19.3.1" + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini fi if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then - echo "---> Upgrading pip to latest version ..." + echo "---> Upgrading pip, setuptools and wheel to latest version ..." if ! pip install -U pip setuptools wheel; then - echo "WARNING: Installation of the latest pip,setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" pip install --isolated -U pip setuptools wheel fi fi @@ -94,8 +91,13 @@ elif [[ -f requirements.txt ]]; then pip install -r requirements.txt fi -if [[ -f setup.py && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then - echo "---> Installing application ..." +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." pip install . fi diff --git a/2.7/s2i/bin/init-wrapper b/3.12-minimal/s2i/bin/init-wrapper similarity index 100% rename from 2.7/s2i/bin/init-wrapper rename to 3.12-minimal/s2i/bin/init-wrapper diff --git a/3.7/s2i/bin/run b/3.12-minimal/s2i/bin/run similarity index 51% rename from 3.7/s2i/bin/run rename to 3.12-minimal/s2i/bin/run index ee1e1d3b..bc0b3a15 100755 --- a/3.7/s2i/bin/run +++ b/3.12-minimal/s2i/bin/run @@ -17,7 +17,9 @@ function should_migrate() { # Guess the number of workers according to the number of cores function get_default_web_concurrency() { - limit_vars=$(cgroup-limits) + # Using python and just python here because the script has + # platform-python in its shebang which we don't have in the minimal image + limit_vars=$(python /usr/bin/cgroup-limits) local $limit_vars if [ -z "${NUMBER_OF_CORES:-}" ]; then echo 1 @@ -43,30 +45,54 @@ 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 + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + APP_HOME=$(readlink -f "${APP_HOME:-.}") # Change the working directory to APP_HOME PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" cd "$APP_HOME" -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" +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 fi - maybe_run_in_init_wrapper "$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) ..." - maybe_run_in_init_wrapper python "$APP_FILE" -else - test -n "$app_file_check" && (>&2 echo "ERROR: file '$app_file_check' not found.") && exit 1 +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi fi # Look for 'manage.py' in the current directory @@ -82,6 +108,11 @@ if should_migrate; then fi fi +# If not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + if is_gunicorn_installed; then setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) # Look for wsgi module in the current directory @@ -94,17 +125,27 @@ if is_gunicorn_installed; then 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" + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" fi fi if is_django_installed; then if [[ -f "$manage_file" ]]; then - echo "---> Serving application with 'manage.py runserver' ..." + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." echo "WARNING: this is NOT a recommended way to run you application in production!" echo "Consider using gunicorn or some other production web server." - maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:8080 + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT else echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." echo "Skipped 'python manage.py runserver'." diff --git a/3.12-minimal/s2i/bin/usage b/3.12-minimal/s2i/bin/usage new file mode 100755 index 00000000..5261cd04 --- /dev/null +++ b/3.12-minimal/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 < -- curl 127.0.0.1:8080 +EOF diff --git a/3.12-minimal/test/__init__.py b/3.12-minimal/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.12-minimal/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/2.7/test/app-home-test-app b/3.12-minimal/test/app-home-test-app similarity index 100% rename from 2.7/test/app-home-test-app rename to 3.12-minimal/test/app-home-test-app diff --git a/3.12-minimal/test/app-module-test-app b/3.12-minimal/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.12-minimal/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/2.7/test/check_imagestreams.py b/3.12-minimal/test/check_imagestreams.py similarity index 100% rename from 2.7/test/check_imagestreams.py rename to 3.12-minimal/test/check_imagestreams.py diff --git a/3.12-minimal/test/conftest.py b/3.12-minimal/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.12-minimal/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.12-minimal/test/django-different-port-test-app b/3.12-minimal/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.12-minimal/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/2.7/test/django-test-app b/3.12-minimal/test/django-test-app similarity index 100% rename from 2.7/test/django-test-app rename to 3.12-minimal/test/django-test-app diff --git a/3.12-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl b/3.12-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl new file mode 120000 index 00000000..f5c5b907 --- /dev/null +++ b/3.12-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/mod_wsgi.Dockerfile.tpl \ No newline at end of file diff --git a/3.12-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl b/3.12-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl new file mode 100644 index 00000000..53a88357 --- /dev/null +++ b/3.12-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl @@ -0,0 +1,20 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +ENV UPGRADE_PIP_TO_LATEST=1 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +# Install packages necessary for compiling uwsgi from source +# pkgconfig(python-3.12) is provided by both python3-devel in c9s +# and python3.12-devel in UBI8. +RUN microdnf install -y gcc "pkgconfig(python-3.12)" which +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run diff --git a/3.12-minimal/test/gunicorn-config-different-port-test-app b/3.12-minimal/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.12-minimal/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.12-minimal/test/gunicorn-different-port-test-app b/3.12-minimal/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.12-minimal/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.12-minimal/test/gunicorn-python-configfile-different-port-test-app b/3.12-minimal/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.12-minimal/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/2.7/test/imagestreams b/3.12-minimal/test/imagestreams similarity index 100% rename from 2.7/test/imagestreams rename to 3.12-minimal/test/imagestreams diff --git a/2.7/test/locale-test-app b/3.12-minimal/test/locale-test-app similarity index 100% rename from 2.7/test/locale-test-app rename to 3.12-minimal/test/locale-test-app diff --git a/2.7/test/micropipenv-requirements-test-app b/3.12-minimal/test/micropipenv-requirements-test-app similarity index 100% rename from 2.7/test/micropipenv-requirements-test-app rename to 3.12-minimal/test/micropipenv-requirements-test-app diff --git a/2.7/test/micropipenv-test-app/.gitignore b/3.12-minimal/test/micropipenv-test-app/.gitignore similarity index 100% rename from 2.7/test/micropipenv-test-app/.gitignore rename to 3.12-minimal/test/micropipenv-test-app/.gitignore diff --git a/2.7/test/micropipenv-test-app/.s2i/environment b/3.12-minimal/test/micropipenv-test-app/.s2i/environment similarity index 100% rename from 2.7/test/micropipenv-test-app/.s2i/environment rename to 3.12-minimal/test/micropipenv-test-app/.s2i/environment diff --git a/3.5/test/pipenv-test-app/Pipfile b/3.12-minimal/test/micropipenv-test-app/Pipfile similarity index 60% rename from 3.5/test/pipenv-test-app/Pipfile rename to 3.12-minimal/test/micropipenv-test-app/Pipfile index 44d25a5f..148759cb 100644 --- a/3.5/test/pipenv-test-app/Pipfile +++ b/3.12-minimal/test/micropipenv-test-app/Pipfile @@ -5,10 +5,11 @@ name = "pypi" [packages] "e1839a8" = {path = ".", editable = true} -requests = "==2.17.0" +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} [dev-packages] pytest = ">=2.8.0" [requires] -python_version = "3.5" +python_version = "3.12" diff --git a/3.12-minimal/test/micropipenv-test-app/Pipfile.lock b/3.12-minimal/test/micropipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..1d3f18e5 --- /dev/null +++ b/3.12-minimal/test/micropipenv-test-app/Pipfile.lock @@ -0,0 +1,211 @@ +{ + "_meta": { + "hash": { + "sha256": "08628d8adcf5d297b100b9dbb14807af951f225a705b902e39726cbbbaf91616" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.11.17" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "gunicorn": { + "hashes": [ + "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0", + "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033" + ], + "markers": "python_version >= '3.5'", + "version": "==21.2.0" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", + "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.18" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pluggy": { + "hashes": [ + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "pytest": { + "hashes": [ + "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", + "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==7.4.3" + } + } +} diff --git a/2.7/test/micropipenv-test-app/setup.py b/3.12-minimal/test/micropipenv-test-app/setup.py similarity index 100% rename from 2.7/test/micropipenv-test-app/setup.py rename to 3.12-minimal/test/micropipenv-test-app/setup.py diff --git a/3.12-minimal/test/micropipenv-test-app/testapp.py b/3.12-minimal/test/micropipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.12-minimal/test/micropipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/2.7/test/numpy-test-app b/3.12-minimal/test/numpy-test-app similarity index 100% rename from 2.7/test/numpy-test-app rename to 3.12-minimal/test/numpy-test-app diff --git a/2.7/test/pipenv-and-micropipenv-should-fail-test-app b/3.12-minimal/test/pipenv-and-micropipenv-should-fail-test-app similarity index 100% rename from 2.7/test/pipenv-and-micropipenv-should-fail-test-app rename to 3.12-minimal/test/pipenv-and-micropipenv-should-fail-test-app diff --git a/2.7/test/pipenv-test-app/.gitignore b/3.12-minimal/test/pipenv-test-app/.gitignore similarity index 100% rename from 2.7/test/pipenv-test-app/.gitignore rename to 3.12-minimal/test/pipenv-test-app/.gitignore diff --git a/2.7/test/pipenv-test-app/.s2i/environment b/3.12-minimal/test/pipenv-test-app/.s2i/environment similarity index 100% rename from 2.7/test/pipenv-test-app/.s2i/environment rename to 3.12-minimal/test/pipenv-test-app/.s2i/environment diff --git a/2.7/test/pipenv-test-app/Pipfile b/3.12-minimal/test/pipenv-test-app/Pipfile similarity index 60% rename from 2.7/test/pipenv-test-app/Pipfile rename to 3.12-minimal/test/pipenv-test-app/Pipfile index 5f1a38e9..148759cb 100644 --- a/2.7/test/pipenv-test-app/Pipfile +++ b/3.12-minimal/test/pipenv-test-app/Pipfile @@ -5,10 +5,11 @@ name = "pypi" [packages] "e1839a8" = {path = ".", editable = true} -requests = "==2.20.0" +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} [dev-packages] pytest = ">=2.8.0" [requires] -python_version = "2.7" +python_version = "3.12" diff --git a/3.12-minimal/test/pipenv-test-app/Pipfile.lock b/3.12-minimal/test/pipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..1d3f18e5 --- /dev/null +++ b/3.12-minimal/test/pipenv-test-app/Pipfile.lock @@ -0,0 +1,211 @@ +{ + "_meta": { + "hash": { + "sha256": "08628d8adcf5d297b100b9dbb14807af951f225a705b902e39726cbbbaf91616" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.11.17" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "gunicorn": { + "hashes": [ + "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0", + "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033" + ], + "markers": "python_version >= '3.5'", + "version": "==21.2.0" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", + "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.18" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pluggy": { + "hashes": [ + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "pytest": { + "hashes": [ + "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", + "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==7.4.3" + } + } +} diff --git a/2.7/test/pipenv-test-app/setup.py b/3.12-minimal/test/pipenv-test-app/setup.py similarity index 100% rename from 2.7/test/pipenv-test-app/setup.py rename to 3.12-minimal/test/pipenv-test-app/setup.py diff --git a/3.12-minimal/test/pipenv-test-app/testapp.py b/3.12-minimal/test/pipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.12-minimal/test/pipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.12-minimal/test/poetry-src-layout-test-app b/3.12-minimal/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.12-minimal/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.12-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.12-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..160e51c4 --- /dev/null +++ b/3.12-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2023.11.14 diff --git a/3.12-minimal/test/pyuwsgi-pipenv-test-app/app.sh b/3.12-minimal/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.12-minimal/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.12-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.12-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.12-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.3/test/uwsgi-test-app/wsgi.py b/3.12-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py similarity index 100% rename from 3.3/test/uwsgi-test-app/wsgi.py rename to 3.12-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py diff --git a/3.12-minimal/test/run b/3.12-minimal/test/run new file mode 100755 index 00000000..8de24fe3 --- /dev/null +++ b/3.12-minimal/test/run @@ -0,0 +1,308 @@ +#!/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. +# +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,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${MINIMAL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) + +# TODO: Make command compatible for Mac users +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile_minimal +" + +if [[ -z $VERSION ]]; then + echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 + exit 1 +fi + +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} + +. test/test-lib.sh + +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 ..." + ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + return 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() { + 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 +} +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + info "Waiting for application container to start $CONTAINER_ARGS ..." + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && return 0 + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return 1 +} + +test_s2i_usage() { + info "Testing 's2i usage' ..." + ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage ..." + docker run --rm ${IMAGE_NAME} &>/dev/null +} + +test_scl_usage() { + local run_cmd="$1" + local expected="$2" + local cid_file="$3" + + info "Testing command availability in the image" + 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="$CID_FILE_DIR"/"$(mktemp -u -p . --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 + # Some test apps have tests in their startup code so we have to check + # that the container starts at all + ct_check_testcase_result $? + + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? + test_connection + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") +} + +test_from_dockerfile(){ + info "Test from Dockerfile" + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? +} + +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" + + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. + + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? + + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? + else + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." + fi + +} + +test_application_with_user() { + # test application with random user + CONTAINER_ARGS="--user 12345" test_application + +} + +test_application_enable_init_wrapper() { + # test application with init wrapper + CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue + + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" + + cleanup ${app} +done + +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.12-minimal/test/run-openshift-pytest b/3.12-minimal/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.12-minimal/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.12-minimal/test/run-pytest b/3.12-minimal/test/run-pytest new file mode 100755 index 00000000..9810fe89 --- /dev/null +++ b/3.12-minimal/test/run-pytest @@ -0,0 +1,16 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +if python3 -c 'import sys; sys.exit(0 if sys.version_info < (3,13) else 1)'; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.12-minimal/test/setup-cfg-test-app b/3.12-minimal/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.12-minimal/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/2.7/test/setup-requirements-test-app b/3.12-minimal/test/setup-requirements-test-app similarity index 100% rename from 2.7/test/setup-requirements-test-app rename to 3.12-minimal/test/setup-requirements-test-app diff --git a/2.7/test/setup-test-app b/3.12-minimal/test/setup-test-app similarity index 100% rename from 2.7/test/setup-test-app rename to 3.12-minimal/test/setup-test-app diff --git a/3.12-minimal/test/standalone-custom-pypi-index-test-app b/3.12-minimal/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.12-minimal/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/2.7/test/standalone-test-app b/3.12-minimal/test/standalone-test-app similarity index 100% rename from 2.7/test/standalone-test-app rename to 3.12-minimal/test/standalone-test-app diff --git a/2.7/test/test-lib.sh b/3.12-minimal/test/test-lib.sh similarity index 100% rename from 2.7/test/test-lib.sh rename to 3.12-minimal/test/test-lib.sh diff --git a/3.12-minimal/test/test_container_application.py b/3.12-minimal/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.12-minimal/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.12-minimal/test/test_container_basics.py b/3.12-minimal/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.12-minimal/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.12-minimal/test/test_ocp_deploy_templates.py b/3.12-minimal/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.12-minimal/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.12-minimal/test/test_ocp_helm_python_django_app.py b/3.12-minimal/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.12-minimal/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.12-minimal/test/test_ocp_helm_python_django_psql_persistent.py b/3.12-minimal/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.12-minimal/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.12-minimal/test/test_ocp_helm_python_imagestreams.py b/3.12-minimal/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.12-minimal/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.12-minimal/test/test_ocp_imagestreams_quickstart.py b/3.12-minimal/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.12-minimal/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.12-minimal/test/test_ocp_python_ex_standalone.py b/3.12-minimal/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.12-minimal/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.12-minimal/test/test_ocp_python_ex_template.py b/3.12-minimal/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.12-minimal/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file diff --git a/3.12-minimal/test/uwsgi-test-app b/3.12-minimal/test/uwsgi-test-app new file mode 120000 index 00000000..c3ccf241 --- /dev/null +++ b/3.12-minimal/test/uwsgi-test-app @@ -0,0 +1 @@ +../../examples/uwsgi-test-app \ No newline at end of file diff --git a/3.12/Dockerfile.c10s b/3.12/Dockerfile.c10s new file mode 100644 index 00000000..0ff5e778 --- /dev/null +++ b/3.12/Dockerfile.c10s @@ -0,0 +1,88 @@ +# This image provides a Python 3.12 environment you can use to run your Python +# applications. +FROM quay.io/sclorg/s2i-base-c10s:c10s + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.12 \ + 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 \ + ARCH=x86_64 + +ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.12" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python312,python-312,rh-python312" \ + com.redhat.component="$NAME" \ + name="sclorg/python-312-c10s" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.12/test/setup-test-app/ $FGC/$NAME python-sample-app" \ + maintainer="SoftwareCollections.org " + +RUN INSTALL_PKGS="python3 python3-devel python3-setuptools python3-pip nss_wrapper-libs \ + httpd httpd-devel mod_ssl mod_auth_gssapi mod_ldap \ + mod_session gcc-gfortran libffi-devel libtool-ltdl krb5-devel" && \ + yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + yum -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.12/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.12/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.12/root/opt/wheels /opt/wheels +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 python3.12 -m venv ${APP_ROOT} && \ +# Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ +# We have to upgrade pip to a newer verison because \ +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ +# support platforms like ppc64le, aarch64 or armv7 \ +# We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ +# because it's tested better then whatever version from PyPI and contains useful patches. \ +# We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ +# to reinstall itself in the next build phases in the assemble script if user wants the latest version \ +${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +rm -r /opt/wheels && \ +chown -R 1001:0 ${APP_ROOT} && \ +fix-permissions ${APP_ROOT} -P && \ +# The following echo adds the unset command for the variables set below to the \ +# venv activation script. This prevents the virtual environment from being \ +# activated multiple times and also every time the prompt is rendered. \ +echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.12/Dockerfile.c9s b/3.12/Dockerfile.c9s new file mode 100644 index 00000000..22461d24 --- /dev/null +++ b/3.12/Dockerfile.c9s @@ -0,0 +1,89 @@ +# This image provides a Python 3.12 environment you can use to run your Python +# applications. +FROM quay.io/sclorg/s2i-base-c9s:c9s + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.12 \ + 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 \ + ARCH=x86_64 + +ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.12" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python312,python-312,rh-python312" \ + com.redhat.component="$NAME" \ + name="sclorg/python-312-c9s" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.12/test/setup-test-app/ $FGC/$NAME python-sample-app" \ + maintainer="SoftwareCollections.org " + +RUN INSTALL_PKGS="python3.12 python3.12-devel python3.12-pip nss_wrapper-libs httpd \ + httpd-devel mod_ssl mod_auth_gssapi mod_ldap mod_session \ + atlas-devel gcc-gfortran libffi-devel libtool-ltdl enchant \ + krb5-devel" && \ + yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + yum -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.12/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.12/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.12/root/opt/wheels /opt/wheels +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 python3.12 -m venv ${APP_ROOT} && \ +# Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ +# We have to upgrade pip to a newer verison because \ +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ +# support platforms like ppc64le, aarch64 or armv7 \ +# We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ +# because it's tested better then whatever version from PyPI and contains useful patches. \ +# We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ +# to reinstall itself in the next build phases in the assemble script if user wants the latest version \ +${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +rm -r /opt/wheels && \ +chown -R 1001:0 ${APP_ROOT} && \ +fix-permissions ${APP_ROOT} -P && \ +# The following echo adds the unset command for the variables set below to the \ +# venv activation script. This prevents the virtual environment from being \ +# activated multiple times and also every time the prompt is rendered. \ +echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.12/Dockerfile.rhel8 b/3.12/Dockerfile.rhel8 new file mode 100644 index 00000000..46cb2abf --- /dev/null +++ b/3.12/Dockerfile.rhel8 @@ -0,0 +1,96 @@ +# This image provides a Python 3.12 environment you can use to run your Python +# applications. +FROM ubi8/s2i-base:1 + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.12 \ + PATH=$HOME/.local/bin/:$PATH \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi8-python-312 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off + +ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.12" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python312,python-312,rh-python312" \ + com.redhat.component="python-312-container" \ + name="ubi8/python-312" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.12/test/setup-test-app/ ubi8/python-312 python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi8-python-312" \ + maintainer="SoftwareCollections.org " + +RUN INSTALL_PKGS="python3.12 python3.12-devel python3.12-setuptools python3.12-pip nss_wrapper-libs \ + httpd httpd-devel mod_ssl mod_auth_gssapi mod_ldap \ + mod_session atlas-devel gcc-gfortran libffi-devel libtool-ltdl \ + enchant krb5-devel" && \ + yum -y module enable httpd:2.4 && \ + yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + # Remove redhat-logos-httpd (httpd dependency) to keep image size smaller. + rpm -e --nodeps redhat-logos-httpd && \ + yum -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.12/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.12/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.12/root/opt/wheels /opt/wheels +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.12 -m venv ${APP_ROOT} && \ + # Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ + # We have to upgrade pip to a newer verison because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions && \ + # The following echo adds the unset command for the variables set below to the \ + # venv activation script. This prevents the virtual environment from being \ + # activated multiple times and also every time the prompt is rendered. \ + echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.12/Dockerfile.rhel9 b/3.12/Dockerfile.rhel9 new file mode 100644 index 00000000..27b6f4de --- /dev/null +++ b/3.12/Dockerfile.rhel9 @@ -0,0 +1,95 @@ +# This image provides a Python 3.12 environment you can use to run your Python +# applications. +FROM ubi9/s2i-base:1 + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.12 \ + PATH=$HOME/.local/bin/:$PATH \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi9-python-312 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off + +ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.12" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python312,python-312,rh-python312" \ + com.redhat.component="python-312-container" \ + name="ubi9/python-312" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.12/test/setup-test-app/ ubi9/python-312 python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi9-python-312" \ + maintainer="SoftwareCollections.org " + +RUN INSTALL_PKGS="python3.12 python3.12-devel python3.12-pip nss_wrapper-libs httpd \ + httpd-devel mod_ssl mod_auth_gssapi mod_ldap mod_session \ + atlas-devel gcc-gfortran libffi-devel libtool-ltdl enchant \ + krb5-devel" && \ + yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + # Remove redhat-logos-httpd (httpd dependency) to keep image size smaller. + rpm -e --nodeps redhat-logos-httpd && \ + yum -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.12/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.12/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.12/root/opt/wheels /opt/wheels +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.12 -m venv ${APP_ROOT} && \ + # Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ + # We have to upgrade pip to a newer verison because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions && \ + # The following echo adds the unset command for the variables set below to the \ + # venv activation script. This prevents the virtual environment from being \ + # activated multiple times and also every time the prompt is rendered. \ + echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/2.7/README.md b/3.12/README.md similarity index 56% rename from 2.7/README.md rename to 3.12/README.md index dde028c0..fb526ac0 100644 --- a/2.7/README.md +++ b/3.12/README.md @@ -1,11 +1,11 @@ -Python 2.7 container image -=================== - -This container image includes Python 2.7 as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 2.7 applications. -Users can choose between RHEL and CentOS based builder images. -The RHEL images are available in the [Red Hat Container Catalog](https://access.redhat.com/containers/), -the CentOS images are available on [Docker Hub](https://hub.docker.com/r/centos/), -and the Fedora images are available in [Fedora Registry](https://registry.fedoraproject.org/). +Python 3.12 container image +========================= + +This container image includes Python 3.12 as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.12 applications. +Users can choose between RHEL, CentOS Stream, and Fedora based builder images. +The RHEL images are available in the [Red Hat Container Catalog](https://catalog.redhat.com/software/containers/explore), +CentOS Stream images are available on [Quay.io](https://quay.io/organization/sclorg), +and the Fedora images are available on [Quay.io](https://quay.io/organization/fedora). The resulting image can be run using [podman](https://github.com/containers/libpod) or [docker](http://docker.io). @@ -14,8 +14,8 @@ Note: while the examples in this README are calling `podman`, you can replace an Description ----------- -Python 2.7 available as container is a base platform for -building and running various Python 2.7 applications and frameworks. +Python 3.12 available as container is a base platform for +building and running various Python 3.12 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, @@ -29,28 +29,132 @@ modules for their web applications. There is no guarantee for any specific npm o version, that is included in the image; those versions can be changed anytime and the nodejs itself is included just to make the npm work. -Usage ---------------------- +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Usage in Openshift +------------------ -For this, we will assume that you are using the supported image, available via `python:2.7` imagestream tag in Openshift. -Building a simple [python-sample-app](https://github.com/sclorg/s2i-python-container/tree/master/2.7/test/setup-test-app) application +For this, we will assume that you are using one of the supported images available via imagestream tags in Openshift, eg. `python:3.12-ubi9` +Building a simple [python-sample-app](https://github.com/sclorg/django-ex.git) application in Openshift can be achieved with the following step: ``` - oc new-app python:2.7~https://github.com/sclorg/s2i-python-container.git --context-dir=2.7/test/setup-test-app/ + oc new-app python:3.12-ubi9~https://github.com/sclorg/django-ex.git ``` -The same application can also be built using the standalone [S2I](https://github.com/openshift/source-to-image) application on systems that have it available: +**Accessing the application:** +``` +$ oc get pods +$ oc exec -- curl 127.0.0.1:8080 +``` - ``` - $ s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=2.7/test/setup-test-app/ python-sample-app - ``` +Source-to-Image framework and scripts +------------------------------------- +This image supports the [Source-to-Image](https://docs.openshift.com/container-platform/4.14/openshift_images/create-images.html#images-create-s2i_create-images) +(S2I) strategy in OpenShift. The Source-to-Image is an OpenShift framework +which makes it easy to write images that take application source code as +an input, use a builder image like this Python container image, and produce +a new image that runs the assembled application as an output. + +To support the Source-to-Image framework, important scripts are included in the builder image: + +* The `/usr/libexec/s2i/assemble` script inside the image is run to produce a new image with the application artifacts. +The script takes sources of a given application and places them into appropriate directories inside the image. +It utilizes some common patterns in Perl application development (see the **Environment variables** section below). +* The `/usr/libexec/s2i/run` script is set as the default command in the resulting container image (the new image with the application artifacts). +It runs your application according to settings in `APP_MODULE`, `APP_FILE` or `APP_SCRIPT` environment variables or it tries to detect the best +way automatically. + +Building an application using a Dockerfile +------------------------------------------ +Compared to the Source-to-Image strategy, using a Dockerfile is a more +flexible way to build a Python container image with an application. +Use a Dockerfile when Source-to-Image is not sufficiently flexible for you or +when you build the image outside of the OpenShift environment. + +To use the Python image in a Dockerfile, follow these steps: +#### 1. Pull a base builder image to build on -Where `` is the s2i-python image you [downloaded from RHEL, Centos or Fedora registry](../README.md#Download) or [built](../README.md#Build) from these sources. For example ubi8/python-36, centos/python-36-centos7 or f31/python3. +``` +podman pull registry.access.redhat.com/ubi9/python-312 +``` + +#### 2. Pull and application code + +An example application available at https://github.com/sclorg/django-ex.git is used here. Feel free to clone the repository for further experiments. +You can also take a look at code examples in s2i-python-container repository: https://github.com/sclorg/s2i-python-container/tree/master/examples + +``` +git clone https://github.com/sclorg/django-ex.git app-src +``` + +#### 3. Prepare an application inside a container + +This step usually consists of at least these parts: + +* putting the application source into the container +* installing the dependencies +* setting the default command in the resulting image + +For all these three parts, users can either setup all manually and use commands `python` and `pip` explicitly in the Dockerfile, +or users can use the Source-to-Image scripts inside the image. + +The manual way comes with the highest level of flexibility but requires you to know how to work +with modules or software collections manually, how to setup virtual environment with the right version +of Python and many more. On the other hand, using Source-to-Image scripts makes your Dockerfile +prepared for a future flawless switch to a newer or different platform. + +To use the Source-to-Image scripts and build an image using a Dockerfile, create a Dockerfile with this content: -**Accessing the application:** ``` -$ curl 127.0.0.1:8080 +FROM registry.access.redhat.com/ubi9/python-312 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you decide not to use the Source-to-Image scripts, you will need to manually tailor the Dockerfile to your application and its needs. +Example Dockerfile for a simple Django application: + +``` +FROM registry.access.redhat.com/ubi9/python-312 + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 +``` + +#### 4. Build a new image from a Dockerfile prepared in the previous step + +``` +podman build -t python-app . +``` + +#### 5. Run the resulting image with final application + +``` +podman run -d python-app ``` Environment variables @@ -86,7 +190,7 @@ file inside your source code repository. 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/sclorg/s2i-python-container/tree/master/2.7/test/setup-test-app). + [setup-test-app](https://github.com/sclorg/s2i-python-container/tree/master/3.12/test/setup-test-app). * **APP_HOME** @@ -115,9 +219,9 @@ file inside your source code repository. 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_SETUP_PY_PROCESSING** +* **DISABLE_SETUP_PY_PROCESSING** / **DISABLE_PYPROJECT_TOML_PROCESSING** - Set this to a non-empty value to skip processing of setup.py script if you + Set this to a non-empty value to skip processing of setup.{py,cfg} or pyproject.toml script if you use `-e .` in requirements.txt to trigger its processing or you don't want your application to be installed into site-packages directory. @@ -126,14 +230,20 @@ file inside your source code repository. Set this variable to use [Pipenv](https://github.com/pypa/pipenv), the higher-level Python packaging tool, to manage dependencies of the application. This should be used only if your project contains properly formated Pipfile - and Pipfile.lock. (Implies `UPGRADE_PIP_TO_LATEST` to satisfy dependencies of - Pipenv.) + and Pipfile.lock. + * **PIN_PIPENV_VERSION** Set this variable together with `ENABLE_PIPENV` to use a specific version of Pipenv. If not set, the latest stable version from PyPI is installed. For example `PIN_PIPENV_VERSION=2018.11.26` installs `pipenv==2018.11.26`. +* **ENABLE_MICROPIPENV** + + Set this variable to use [micropipenv](https://github.com/thoth-station/micropipenv), + a lightweight wrapper for pip to support requirements.txt, Pipenv and Poetry lock + files or converting them to pip-tools compatible output. Designed for containerized Python applications. + Available only for Python 3 images. * **ENABLE_INIT_WRAPPER** @@ -153,12 +263,18 @@ file inside your source code repository. the custom index, the container will try to install/update them from upstream PyPI afterwards. +* **PORT** + + HTTP(S) port your application should listen on. The default is 8080. + `PORT` is used only for Django development server and for Gunicorn + with the default configutation (no `APP_CONFIG` or `GUNICORN_CMD_ARGS` specified). + * **UPGRADE_PIP_TO_LATEST** Set this variable to a non-empty value to have the 'pip' program and related python packages (setuptools and wheel) 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. + before any Python packages are installed. If not set, the container will use + the stable pip version this container was built with, taken from a recent Fedora release. * **WEB_CONCURRENCY** @@ -191,7 +307,7 @@ However, if these files exist they will affect the behavior of the build process Configures various aspects of the project, including installation of dependencies, as documented - [here](https://packaging.python.org/en/latest/distributing.html#setup-py). + [here](https://packaging.python.org/guides/distributing-packages-using-setuptools/?highlight=distributing#setup-py). For most projects, it is sufficient to simply use `requirements.txt` or `Pipfile`. Set `DISABLE_SETUP_PY_PROCESSING` environment variable to true in order to skip processing of this file. @@ -216,6 +332,9 @@ following ways, in precedence order: If you have both Django and Gunicorn in your requirements, your Django project will automatically be served using Gunicorn. + The default setting for Gunicorn (`--bind=0.0.0.0:$PORT --access-logfile=-`) is applied + only if both `$APP_CONFIG` and `$GUNICORN_CMD_ARGS` are not defined. + * **Django development server** If you have Django in your requirements but don't have Gunicorn, then your @@ -239,7 +358,7 @@ following ways, in precedence order: application. Hot deploy ---------------------- +---------- If you are using Django, hot deploy will work out of the box. @@ -250,7 +369,7 @@ 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 podman's (or docker's) -[exec](https://github.com/containers/libpod/blob/master/docs/podman-exec.1.md) command: +[exec](https://github.com/containers/podman/blob/main/docs/source/markdown/podman-exec.1.md) command: ``` podman exec -it /bin/bash @@ -264,5 +383,6 @@ See also -------- Dockerfile and other sources are available on https://github.com/sclorg/s2i-python-container. In that repository you also can find another versions of Python environment Dockerfiles. -Dockerfile for CentOS is called `Dockerfile`, Dockerfile for RHEL7 is called `Dockerfile.rhel7`, -for RHEL8 it's `Dockerfile.rhel8` and the Fedora Dockerfile is called `Dockerfile.fedora`. +Dockerfile for RHEL8 is called `Dockerfile.rhel8`, for RHEL9 it's `Dockerfile.rhel9`, +for CentOS Stream 9 it's `Dockerfile.c9s`, for CentOS Stream 10 it's `Dockerfile.c10s`, +and the Fedora Dockerfile is called `Dockerfile.fedora`. diff --git a/2.7/root/opt/app-root/etc/generate_container_user b/3.12/root/opt/app-root/etc/generate_container_user similarity index 90% rename from 2.7/root/opt/app-root/etc/generate_container_user rename to 3.12/root/opt/app-root/etc/generate_container_user index 43ee7ecb..a7fd74d6 100644 --- a/2.7/root/opt/app-root/etc/generate_container_user +++ b/3.12/root/opt/app-root/etc/generate_container_user @@ -9,7 +9,7 @@ if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"1001" ]; then 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 + echo "default:x:${USER_ID}:${GROUP_ID}:Default Application User:${HOME}:/bin/bash" >> $NSS_WRAPPER_PASSWD export NSS_WRAPPER_PASSWD export NSS_WRAPPER_GROUP diff --git a/3.12/root/opt/wheels b/3.12/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.12/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.8/s2i/bin/assemble b/3.12/s2i/bin/assemble similarity index 82% rename from 3.8/s2i/bin/assemble rename to 3.12/s2i/bin/assemble index 5b33c2da..34a68dbb 100755 --- a/3.8/s2i/bin/assemble +++ b/3.12/s2i/bin/assemble @@ -11,7 +11,7 @@ function should_collectstatic() { function virtualenv_bin() { # New versions of Python (>3.6) should use venv module # from stdlib instead of virtualenv package - python3.8 -m venv $1 + python3.12 -m venv $1 } # Install pipenv or micropipenv to the separate virtualenv to isolate it @@ -53,20 +53,17 @@ mv /tmp/src/* "$HOME" # set permissions for any installed artifacts fix-permissions /opt/app-root -P -# We have to first upgrade pip to at least 19.3 because: -# * pip < 9 does not support different packages' versions for Python 2/3 -# * pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 wheels -# support platforms like ppc64le, aarch64 or armv7 -echo "---> Upgrading pip to version 19.3.1 ..." -if ! pip install -U "pip==19.3.1"; then - echo "WARNING: Installation of 'pip==19.3.1' failed, trying again from official PyPI with pip --isolated install" - pip install --isolated -U "pip==19.3.1" + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini fi if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then - echo "---> Upgrading pip to latest version ..." + echo "---> Upgrading pip, setuptools and wheel to latest version ..." if ! pip install -U pip setuptools wheel; then - echo "WARNING: Installation of the latest pip,setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" pip install --isolated -U pip setuptools wheel fi fi @@ -94,8 +91,13 @@ elif [[ -f requirements.txt ]]; then pip install -r requirements.txt fi -if [[ -f setup.py && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then - echo "---> Installing application ..." +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." pip install . fi diff --git a/3.5/s2i/bin/init-wrapper b/3.12/s2i/bin/init-wrapper similarity index 100% rename from 3.5/s2i/bin/init-wrapper rename to 3.12/s2i/bin/init-wrapper diff --git a/3.5/s2i/bin/run b/3.12/s2i/bin/run similarity index 54% rename from 3.5/s2i/bin/run rename to 3.12/s2i/bin/run index ee1e1d3b..7003f038 100755 --- a/3.5/s2i/bin/run +++ b/3.12/s2i/bin/run @@ -43,30 +43,54 @@ 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 + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + APP_HOME=$(readlink -f "${APP_HOME:-.}") # Change the working directory to APP_HOME PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" cd "$APP_HOME" -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" +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 fi - maybe_run_in_init_wrapper "$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) ..." - maybe_run_in_init_wrapper python "$APP_FILE" -else - test -n "$app_file_check" && (>&2 echo "ERROR: file '$app_file_check' not found.") && exit 1 +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi fi # Look for 'manage.py' in the current directory @@ -82,6 +106,11 @@ if should_migrate; then fi fi +# If not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + if is_gunicorn_installed; then setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) # Look for wsgi module in the current directory @@ -94,17 +123,27 @@ if is_gunicorn_installed; then 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" + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" fi fi if is_django_installed; then if [[ -f "$manage_file" ]]; then - echo "---> Serving application with 'manage.py runserver' ..." + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." echo "WARNING: this is NOT a recommended way to run you application in production!" echo "Consider using gunicorn or some other production web server." - maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:8080 + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT else echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." echo "Skipped 'python manage.py runserver'." diff --git a/3.12/s2i/bin/usage b/3.12/s2i/bin/usage new file mode 100755 index 00000000..ef36d6fc --- /dev/null +++ b/3.12/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 < -- curl 127.0.0.1:8080 +EOF diff --git a/3.12/test/__init__.py b/3.12/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.12/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/3.4/test/app-home-test-app b/3.12/test/app-home-test-app similarity index 100% rename from 3.4/test/app-home-test-app rename to 3.12/test/app-home-test-app diff --git a/3.12/test/app-module-test-app b/3.12/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.12/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/3.7/test/check_imagestreams.py b/3.12/test/check_imagestreams.py similarity index 100% rename from 3.7/test/check_imagestreams.py rename to 3.12/test/check_imagestreams.py diff --git a/3.12/test/conftest.py b/3.12/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.12/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.12/test/django-different-port-test-app b/3.12/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.12/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/3.4/test/django-test-app b/3.12/test/django-test-app similarity index 100% rename from 3.4/test/django-test-app rename to 3.12/test/django-test-app diff --git a/3.12/test/from-dockerfile/Dockerfile.tpl b/3.12/test/from-dockerfile/Dockerfile.tpl new file mode 120000 index 00000000..ba88ccc4 --- /dev/null +++ b/3.12/test/from-dockerfile/Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/Dockerfile.tpl \ No newline at end of file diff --git a/3.12/test/from-dockerfile/Dockerfile_no_s2i.tpl b/3.12/test/from-dockerfile/Dockerfile_no_s2i.tpl new file mode 100644 index 00000000..da2f43ff --- /dev/null +++ b/3.12/test/from-dockerfile/Dockerfile_no_s2i.tpl @@ -0,0 +1,16 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 diff --git a/3.12/test/gunicorn-config-different-port-test-app b/3.12/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.12/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.12/test/gunicorn-different-port-test-app b/3.12/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.12/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.12/test/gunicorn-python-configfile-different-port-test-app b/3.12/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.12/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/3.7/test/imagestreams b/3.12/test/imagestreams similarity index 100% rename from 3.7/test/imagestreams rename to 3.12/test/imagestreams diff --git a/3.4/test/locale-test-app b/3.12/test/locale-test-app similarity index 100% rename from 3.4/test/locale-test-app rename to 3.12/test/locale-test-app diff --git a/3.7/test/micropipenv-requirements-test-app b/3.12/test/micropipenv-requirements-test-app similarity index 100% rename from 3.7/test/micropipenv-requirements-test-app rename to 3.12/test/micropipenv-requirements-test-app diff --git a/3.3/test/setup-test-app/.gitignore b/3.12/test/micropipenv-test-app/.gitignore similarity index 100% rename from 3.3/test/setup-test-app/.gitignore rename to 3.12/test/micropipenv-test-app/.gitignore diff --git a/3.7/test/micropipenv-test-app/.s2i/environment b/3.12/test/micropipenv-test-app/.s2i/environment similarity index 100% rename from 3.7/test/micropipenv-test-app/.s2i/environment rename to 3.12/test/micropipenv-test-app/.s2i/environment diff --git a/3.4/test/pipenv-test-app/Pipfile b/3.12/test/micropipenv-test-app/Pipfile similarity index 60% rename from 3.4/test/pipenv-test-app/Pipfile rename to 3.12/test/micropipenv-test-app/Pipfile index ec72c056..148759cb 100644 --- a/3.4/test/pipenv-test-app/Pipfile +++ b/3.12/test/micropipenv-test-app/Pipfile @@ -5,10 +5,11 @@ name = "pypi" [packages] "e1839a8" = {path = ".", editable = true} -requests = "==2.17.0" +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} [dev-packages] pytest = ">=2.8.0" [requires] -python_version = "3.4" +python_version = "3.12" diff --git a/3.12/test/micropipenv-test-app/Pipfile.lock b/3.12/test/micropipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..1d3f18e5 --- /dev/null +++ b/3.12/test/micropipenv-test-app/Pipfile.lock @@ -0,0 +1,211 @@ +{ + "_meta": { + "hash": { + "sha256": "08628d8adcf5d297b100b9dbb14807af951f225a705b902e39726cbbbaf91616" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.11.17" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "gunicorn": { + "hashes": [ + "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0", + "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033" + ], + "markers": "python_version >= '3.5'", + "version": "==21.2.0" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", + "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.18" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pluggy": { + "hashes": [ + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "pytest": { + "hashes": [ + "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", + "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==7.4.3" + } + } +} diff --git a/3.3/test/setup-test-app/setup.py b/3.12/test/micropipenv-test-app/setup.py similarity index 100% rename from 3.3/test/setup-test-app/setup.py rename to 3.12/test/micropipenv-test-app/setup.py diff --git a/3.12/test/micropipenv-test-app/testapp.py b/3.12/test/micropipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.12/test/micropipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/2.7/test/mod-wsgi-test-app b/3.12/test/mod-wsgi-test-app similarity index 100% rename from 2.7/test/mod-wsgi-test-app rename to 3.12/test/mod-wsgi-test-app diff --git a/2.7/test/npm-virtualenv-uwsgi-test-app b/3.12/test/npm-virtualenv-uwsgi-test-app similarity index 100% rename from 2.7/test/npm-virtualenv-uwsgi-test-app rename to 3.12/test/npm-virtualenv-uwsgi-test-app diff --git a/3.4/test/numpy-test-app b/3.12/test/numpy-test-app similarity index 100% rename from 3.4/test/numpy-test-app rename to 3.12/test/numpy-test-app diff --git a/3.12/test/pin-pipenv-version-test-app/.s2i/environment b/3.12/test/pin-pipenv-version-test-app/.s2i/environment new file mode 100644 index 00000000..160e51c4 --- /dev/null +++ b/3.12/test/pin-pipenv-version-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2023.11.14 diff --git a/3.12/test/pin-pipenv-version-test-app/app.sh b/3.12/test/pin-pipenv-version-test-app/app.sh new file mode 100755 index 00000000..4334c760 --- /dev/null +++ b/3.12/test/pin-pipenv-version-test-app/app.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.3/test/uwsgi-test-app/requirements.txt b/3.12/test/pin-pipenv-version-test-app/requirements.txt similarity index 100% rename from 3.3/test/uwsgi-test-app/requirements.txt rename to 3.12/test/pin-pipenv-version-test-app/requirements.txt diff --git a/3.3/test/mod-wsgi-test-app/wsgi.py b/3.12/test/pin-pipenv-version-test-app/wsgi.py similarity index 68% rename from 3.3/test/mod-wsgi-test-app/wsgi.py rename to 3.12/test/pin-pipenv-version-test-app/wsgi.py index 9e724694..96e1a614 100644 --- a/3.3/test/mod-wsgi-test-app/wsgi.py +++ b/3.12/test/pin-pipenv-version-test-app/wsgi.py @@ -3,7 +3,7 @@ @application.route('/') def hello(): - return b'Hello World from mod_wsgi hosted WSGI application!' + return b'Hello World from uWSGI hosted WSGI application!' if __name__ == '__main__': application.run() diff --git a/3.7/test/pipenv-and-micropipenv-should-fail-test-app b/3.12/test/pipenv-and-micropipenv-should-fail-test-app similarity index 100% rename from 3.7/test/pipenv-and-micropipenv-should-fail-test-app rename to 3.12/test/pipenv-and-micropipenv-should-fail-test-app diff --git a/3.4/test/pipenv-test-app/.gitignore b/3.12/test/pipenv-test-app/.gitignore similarity index 100% rename from 3.4/test/pipenv-test-app/.gitignore rename to 3.12/test/pipenv-test-app/.gitignore diff --git a/3.4/test/pipenv-test-app/.s2i/environment b/3.12/test/pipenv-test-app/.s2i/environment similarity index 100% rename from 3.4/test/pipenv-test-app/.s2i/environment rename to 3.12/test/pipenv-test-app/.s2i/environment diff --git a/3.12/test/pipenv-test-app/Pipfile b/3.12/test/pipenv-test-app/Pipfile new file mode 100644 index 00000000..148759cb --- /dev/null +++ b/3.12/test/pipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.12" diff --git a/3.12/test/pipenv-test-app/Pipfile.lock b/3.12/test/pipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..1d3f18e5 --- /dev/null +++ b/3.12/test/pipenv-test-app/Pipfile.lock @@ -0,0 +1,211 @@ +{ + "_meta": { + "hash": { + "sha256": "08628d8adcf5d297b100b9dbb14807af951f225a705b902e39726cbbbaf91616" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.11.17" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "gunicorn": { + "hashes": [ + "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0", + "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033" + ], + "markers": "python_version >= '3.5'", + "version": "==21.2.0" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", + "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.18" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pluggy": { + "hashes": [ + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "pytest": { + "hashes": [ + "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", + "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==7.4.3" + } + } +} diff --git a/3.4/test/pipenv-test-app/setup.py b/3.12/test/pipenv-test-app/setup.py similarity index 100% rename from 3.4/test/pipenv-test-app/setup.py rename to 3.12/test/pipenv-test-app/setup.py diff --git a/3.12/test/pipenv-test-app/testapp.py b/3.12/test/pipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.12/test/pipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.12/test/poetry-src-layout-test-app b/3.12/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.12/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.12/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.12/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..160e51c4 --- /dev/null +++ b/3.12/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2023.11.14 diff --git a/3.12/test/pyuwsgi-pipenv-test-app/app.sh b/3.12/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.12/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.12/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.12/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.12/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.12/test/pyuwsgi-pipenv-test-app/wsgi.py b/3.12/test/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.12/test/pyuwsgi-pipenv-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() diff --git a/3.12/test/run b/3.12/test/run new file mode 100755 index 00000000..134639eb --- /dev/null +++ b/3.12/test/run @@ -0,0 +1,312 @@ +#!/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. +# +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,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${FULL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) + +# TODO: Make command compatible for Mac users +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile +" + +if [[ -z $VERSION ]]; then + echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 + exit 1 +fi + +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} + +. test/test-lib.sh + +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 ..." + ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + return 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() { + 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 +} +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + info "Waiting for application container to start $CONTAINER_ARGS ..." + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && return 0 + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return 1 +} + +test_s2i_usage() { + info "Testing 's2i usage' ..." + ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage ..." + docker run --rm ${IMAGE_NAME} &>/dev/null +} + +test_scl_usage() { + local run_cmd="$1" + local expected="$2" + local cid_file="$3" + + info "Testing command availability in the image" + 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="$CID_FILE_DIR"/"$(mktemp -u -p . --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 + # Some test apps have tests in their startup code so we have to check + # that the container starts at all + ct_check_testcase_result $? + + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? + test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" + ct_check_testcase_result $? + test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" + ct_check_testcase_result $? + test_connection + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") +} + +test_from_dockerfile(){ + info "Test from Dockerfile" + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? +} + +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" + + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. + + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? + + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? + else + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." + fi + +} + +test_application_with_user() { + # test application with random user + CONTAINER_ARGS="--user 12345" test_application + +} + +test_application_enable_init_wrapper() { + # test application with init wrapper + CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue + + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" + + cleanup ${app} +done + +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.12/test/run-openshift-pytest b/3.12/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.12/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.12/test/run-pytest b/3.12/test/run-pytest new file mode 100755 index 00000000..9810fe89 --- /dev/null +++ b/3.12/test/run-pytest @@ -0,0 +1,16 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +if python3 -c 'import sys; sys.exit(0 if sys.version_info < (3,13) else 1)'; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.12/test/setup-cfg-test-app b/3.12/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.12/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/3.4/test/setup-requirements-test-app b/3.12/test/setup-requirements-test-app similarity index 100% rename from 3.4/test/setup-requirements-test-app rename to 3.12/test/setup-requirements-test-app diff --git a/3.4/test/setup-test-app b/3.12/test/setup-test-app similarity index 100% rename from 3.4/test/setup-test-app rename to 3.12/test/setup-test-app diff --git a/3.12/test/standalone-custom-pypi-index-test-app b/3.12/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.12/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/3.4/test/standalone-test-app b/3.12/test/standalone-test-app similarity index 100% rename from 3.4/test/standalone-test-app rename to 3.12/test/standalone-test-app diff --git a/3.4/test/test-lib.sh b/3.12/test/test-lib.sh similarity index 100% rename from 3.4/test/test-lib.sh rename to 3.12/test/test-lib.sh diff --git a/3.12/test/test_container_application.py b/3.12/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.12/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.12/test/test_container_basics.py b/3.12/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.12/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.12/test/test_ocp_deploy_templates.py b/3.12/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.12/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.12/test/test_ocp_helm_python_django_app.py b/3.12/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.12/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.12/test/test_ocp_helm_python_django_psql_persistent.py b/3.12/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.12/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.12/test/test_ocp_helm_python_imagestreams.py b/3.12/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.12/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.12/test/test_ocp_imagestreams_quickstart.py b/3.12/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.12/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.12/test/test_ocp_python_ex_standalone.py b/3.12/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.12/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.12/test/test_ocp_python_ex_template.py b/3.12/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.12/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file diff --git a/3.13-minimal/Dockerfile.c10s b/3.13-minimal/Dockerfile.c10s new file mode 100644 index 00000000..34031864 --- /dev/null +++ b/3.13-minimal/Dockerfile.c10s @@ -0,0 +1,105 @@ +FROM quay.io/centos/centos:stream10-minimal + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.13 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi10-python-313 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el10" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.13" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python313,python-313,rh-python313" \ + com.redhat.component="python-313-container" \ + name="sclorg/python-313-minimal-c10s" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.13-minimal/test/setup-test-app/ ubi10/python-313-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi10-python-313-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.13 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y install --setopt=install_weak_deps=0 epel-release && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.13-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.13-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.13-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.13 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.13-minimal/Dockerfile.fedora b/3.13-minimal/Dockerfile.fedora new file mode 100644 index 00000000..16f4e382 --- /dev/null +++ b/3.13-minimal/Dockerfile.fedora @@ -0,0 +1,104 @@ +FROM quay.io/fedora/fedora-minimal:42 + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.13 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi-python-313 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.13" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python313,python-313,rh-python313" \ + com.redhat.component="python-313-container" \ + name="fedora/python-313-minimal" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.13-minimal/test/setup-test-app/ ubi/python-313-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi-python-313-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.13 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.13-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.13-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.13-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.13 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.13-minimal/README.md b/3.13-minimal/README.md new file mode 100644 index 00000000..d9c56abb --- /dev/null +++ b/3.13-minimal/README.md @@ -0,0 +1,125 @@ +Python 3.13 container image - minimal version +============================================ + +This container image is a special version of the [full Python 3.13 container image](https://github.com/sclorg/s2i-python-container/tree/master/3.13) +provided as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.13 applications. + +Because the minimal and full images work similarly, we document here only the differences and limitations +of the minimal container image. For the documentation of common features see the [full container image docs](https://github.com/sclorg/s2i-python-container/tree/master/3.13). + +The Python 3.13 minimal container image is currently considered a tech-preview and only available on quay.io. +The image is built on top of the [official CentOS Stream base containers](quay.io/centos/centos). + +To pull the Python 3.13 minimal container image to build on, run + +``` +podman pull quay.io/fedora/python-313-minimal +``` + +Description +----------- + +The full container image is a universal base image to build your containerized applications on top of. However, its universal nature +means that the resulting containers it produces consume a lot of disk space. This is caused mainly by the fact that the image contains +npm, compilers, header files and some other packages one might need to install and deploy their applications. + +Because size does matter for us and our customers, we have prepared this minimal container image with very limited subset +of installed packages. There are no compilers, no header files, no npm etc and the yum package manager is replaced with a minimalistic +reimplementation called microdnf, making the resulting container images much smaller. This creates some limitations +but we provide ways to workaround them. + +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Limitations +----------- + +1. There is only a very limited subset of packages installed. They are choosen carefully to satisfy most of the Python apps but your app might have some special needs. +1. There is no npm and nodejs. +1. There are no compilers and header files. Installation from Python wheels should still work but compilation from a source code is not supported out of the box. + +In the next chapter, we provide three possible workarounds for the mentioned limitations of the minimal container image. + +Possible solutions for the limitations +-------------------------------------- + +### Use the full container image + +It's easy at that. If you don't want to write your own Dockerfile and disk space is not a problem, use +the full universal container image and you should be fine. + +### Build your own container image on top of the minimal container image + +Let's say that your application depends on uwsgi. uwsgi cannot be installed from Python wheel and has to be +compiled from source which requires some additional packages to be installed - namely gcc for the compilation +itself and python3.13-devel containing Python header files. + +To solve that problem, you can use all the pieces provided by the minimal container image and just add one more +step to install the missing dependencies: + +``` +FROM python-313-minimal + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src + +# Install packages necessary for compiling uwsgi from source +RUN microdnf install -y gcc python3.13-devel +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you do it this way, your problem with the missing packages is solved. But there is also one disadvantage: the resulting +runtime image contains unnecessary compiler and Python header files. How to solve this? Uninstalling them at the end +of the Dockerfile is not really a solution but we have one. Keep reading. + +### Build on full image, run on minimal image + +Did you know that you can copy files from one image to another one during a build? That's the feature we are gonna use now. +We use the full container image with all compilers and other usefull packages installed to build our app and its dependencies +and we then move the result including the whole virtual environemnt to the minimal container image. + +This app needs mod_wsgi and to install (compile it from source) it, we'll need: httpd-devel for header files, gcc and redhat-rpm-config +as a compiler and configuratuion and finally python3.13-devel containing Python header files. There is no need to install those packages +manually because the full container image already contains them. However, the application needs httpd as a runtime dependency +so we need to install it to the minimal container image as well. + +``` +# Part 1 - build + +FROM python-313 as builder + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the application's dependencies from PyPI +RUN /usr/libexec/s2i/assemble + +# Part 2 - deploy + +FROM python-313-minimal + +# Copy app sources together with the whole virtual environment from the builder image +COPY --from=builder $APP_ROOT $APP_ROOT + +# Install httpd package - runtime dependency of our application +USER 0 +RUN microdnf install -y httpd +USER 1001 + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +This way, the resulting container image does contain only necessary dependencies and it's much lighter. diff --git a/3.3/contrib/etc/generate_container_user b/3.13-minimal/root/opt/app-root/etc/generate_container_user similarity index 90% rename from 3.3/contrib/etc/generate_container_user rename to 3.13-minimal/root/opt/app-root/etc/generate_container_user index 43ee7ecb..a7fd74d6 100644 --- a/3.3/contrib/etc/generate_container_user +++ b/3.13-minimal/root/opt/app-root/etc/generate_container_user @@ -9,7 +9,7 @@ if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"1001" ]; then 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 + echo "default:x:${USER_ID}:${GROUP_ID}:Default Application User:${HOME}:/bin/bash" >> $NSS_WRAPPER_PASSWD export NSS_WRAPPER_PASSWD export NSS_WRAPPER_GROUP diff --git a/3.13-minimal/root/opt/wheels b/3.13-minimal/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.13-minimal/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.13-minimal/root/usr/bin/cgroup-limits b/3.13-minimal/root/usr/bin/cgroup-limits new file mode 100755 index 00000000..6fa19153 --- /dev/null +++ b/3.13-minimal/root/usr/bin/cgroup-limits @@ -0,0 +1,192 @@ +#!/usr/libexec/platform-python + +""" +Script for parsing cgroup information + +This script will read some limits from the cgroup system and parse +them, printing out "VARIABLE=VALUE" on each line for every limit that is +successfully read. Output of this script can be directly fed into +bash's export command. Recommended usage from a bash script: + + set -o errexit + export_vars=$(cgroup-limits) ; export $export_vars + +Variables currently supported: + MAX_MEMORY_LIMIT_IN_BYTES + Maximum possible limit MEMORY_LIMIT_IN_BYTES can have. This is + currently constant value of 9223372036854775807. + MEMORY_LIMIT_IN_BYTES + Maximum amount of user memory in bytes. If this value is set + to the same value as MAX_MEMORY_LIMIT_IN_BYTES, it means that + there is no limit set. The value is taken from + /sys/fs/cgroup/memory/memory.limit_in_bytes for cgroups v1 + and from /sys/fs/cgroup/memory.max for cgroups v2 + NUMBER_OF_CORES + Number of CPU cores that can be used. If both the cpu and cpuset + controllers specify a limit, the controller with the lowest CPU + limit takes precedence. For the cpu controller, the value is + calculated from /sys/fs/cgroup/cpu/cpu.cfs_{quota,period}_us for + cgroups v1 and from /sys/fs/cgroup/cpuset.cpus.effective for cgroups v2. + For the cpuset controller, the value is taken from + /sys/fs/cgroup/cpuset/cpuset.cpus for cgroups v1 and from + /sys/fs/cgroup/cpuset.cpus.effective for cgroups v2 + NO_MEMORY_LIMIT + Set to "true" if MEMORY_LIMIT_IN_BYTES is so high that the caller + can act as if no memory limit was set. Undefined otherwise. + +Note about non-root containers: + + Per podman-run(1) man page, on some systems, changing the resource limits + may not be allowed for non-root users. For more details, see + https://github.com/containers/podman/blob/main/troubleshooting.md#26-running-containers-with-resource-limits-fails-with-a-permissions-error. + +How to test this script: + + Run a container as root and see whether the output of available memory + and CPUs match what is either available on the host or specified via + cgroups limits by the container runtime (podman). + + For example: + + # This should return NO_MEMORY_LIMIT=true, NUMBER_OF_CORES= + sudo podman run -ti --rm quay.io/fedora/s2i-core /usr/bin/cgroup-limits + + # This should return MEMORY_LIMIT_IN_BYTES=2147483648, NUMBER_OF_CORES=3 + # 3 CPUs despite --cpus 4 was given is correct, because the cpuset 2-4 means + # running on 3 concrete processors only, which is lower limit than --cpus + # and thus is preferred + sudo podman run -ti --rm --cpus 4 --cpuset-cpus=2-4 --memory 2G quay.io/fedora/s2i-core /usr/bin/cgroup-limits +""" + +from __future__ import division +from __future__ import print_function +import sys +import subprocess + + +def _read_file(path): + try: + with open(path, 'r') as f: + return f.read().strip() + except IOError: + return None + + +def get_memory_limit(): + """ + Read memory limit, in bytes. + """ + + limit = _read_file('/sys/fs/cgroup/memory/memory.limit_in_bytes') + # If first file does not exist, try cgroups v2 file + limit = limit or _read_file('/sys/fs/cgroup/memory.max') + if limit is None or not limit.isdigit(): + if limit == 'max': + return 9223372036854775807 + print("Warning: Can't detect memory limit from cgroups", + file=sys.stderr) + return None + return int(limit) + + +def get_number_of_cores(): + """ + Read number of CPU cores. + """ + + limits = [l for l in [_read_cpu_quota(), _read_cpuset_size(), _read_nproc()] if l] + if not limits: + return None + return min(limits) + + +def _read_cpu_quota(): + + quota, period = None, None + + quota = _read_file("/sys/fs/cgroup/cpu/cpu.cfs_quota_us") + if quota: + # cgroups v1 + quota = quota.strip() + if quota == "-1": + return None + + period = _read_file("/sys/fs/cgroup/cpu/cpu.cfs_period_us") + if period: + period = period.strip() + + else: + # cgroups v2 + line = _read_file("/sys/fs/cgroup/cpu.max") + if line: + fields = line.split() + + if len(fields) >= 2: + quota = fields[0] + if quota == "max": + return None + period = fields[1] + + + if quota and quota.isdigit() and period and period.isdigit(): + return int(quota)//int(period) + + print("Warning: Can't detect cpu quota from cgroups", + file=sys.stderr) + return None + + +def _read_cpuset_size(): + + core_count = 0 + + line = _read_file('/sys/fs/cgroup/cpuset/cpuset.cpus') + # If first file does not exist, try cgroups v2 file + line = line or _read_file('/sys/fs/cgroup/cpuset.cpus.effective') + if line is None: + # None of the files above exists when running podman as non-root, + # so in that case, this warning is printed every-time + print("Warning: Can't detect cpuset size from cgroups, will use nproc", + file=sys.stderr) + return None + + for group in line.split(','): + core_ids = list(map(int, group.split('-'))) + if len(core_ids) == 2: + core_count += core_ids[1] - core_ids[0] + 1 + else: + core_count += 1 + + return core_count + + +def _read_nproc(): + """ + Returns number of cores without looking at cgroup limits. + This might work as the last resort when running a container as non-root + where cgroups v2 resource limits cannot be set without a specific + configuration (per podman-run(1) man page). + """ + try: + stdout, stderr = subprocess.Popen('nproc', stdout=subprocess.PIPE).communicate() + return int(stdout) + except EnvironmentError as e: + if e.errno != errno.ENOENT: + raise + return None + + +if __name__ == "__main__": + env_vars = { + "MAX_MEMORY_LIMIT_IN_BYTES": 9223372036854775807, + "MEMORY_LIMIT_IN_BYTES": get_memory_limit(), + "NUMBER_OF_CORES": get_number_of_cores() + } + + env_vars = {k: v for k, v in env_vars.items() if v is not None} + + if env_vars.get("MEMORY_LIMIT_IN_BYTES", 0) >= 92233720368547: + env_vars["NO_MEMORY_LIMIT"] = "true" + + for key, value in env_vars.items(): + print("{0}={1}".format(key, value)) diff --git a/3.13-minimal/root/usr/bin/fix-permissions b/3.13-minimal/root/usr/bin/fix-permissions new file mode 100755 index 00000000..ddd33ace --- /dev/null +++ b/3.13-minimal/root/usr/bin/fix-permissions @@ -0,0 +1,27 @@ +#!/bin/sh + +# Allow this script to fail without failing a build +set +e + +SYMLINK_OPT=${2:--L} + +# Fix permissions on the given directory or file to allow group read/write of +# regular files and execute of directories. + +[ $(id -u) -ne 0 ] && CHECK_OWNER=" -uid $(id -u)" + +# If argument does not exist, script will still exit with 0, +# but at least we'll see something went wrong in the log +if ! [ -e "$1" ] ; then + echo "ERROR: File or directory $1 does not exist." >&2 + # We still want to end successfully + exit 0 +fi + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} \! -gid 0 -exec chgrp 0 {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} \! -perm -g+rw -exec chmod g+rw {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} -perm /u+x -a \! -perm /g+x -exec chmod g+x {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} -type d \! -perm /g+x -exec chmod g+x {} + + +# Always end successfully +exit 0 diff --git a/3.13-minimal/root/usr/bin/rpm-file-permissions b/3.13-minimal/root/usr/bin/rpm-file-permissions new file mode 100755 index 00000000..8be1fb0d --- /dev/null +++ b/3.13-minimal/root/usr/bin/rpm-file-permissions @@ -0,0 +1,21 @@ +#!/bin/sh + +CHECK_DIRS="/ /opt /etc /usr /usr/bin /usr/lib /usr/lib64 /usr/share /usr/libexec" + +rpm_format="[%{FILESTATES:fstate} %7{FILEMODES:octal} %{FILENAMES:shescape}\n]" + +rpm -q --qf "$rpm_format" filesystem | while read line +do + eval "set -- $line" + + case $1 in + normal) ;; + *) continue ;; + esac + + case " $CHECK_DIRS " in + *" $3 "*) + chmod "${2: -4}" "$3" + ;; + esac +done diff --git a/2.7/s2i/bin/assemble b/3.13-minimal/s2i/bin/assemble similarity index 76% rename from 2.7/s2i/bin/assemble rename to 3.13-minimal/s2i/bin/assemble index 0daa7a28..f415fe30 100755 --- a/2.7/s2i/bin/assemble +++ b/3.13-minimal/s2i/bin/assemble @@ -9,13 +9,9 @@ function should_collectstatic() { } function virtualenv_bin() { - if head "/etc/redhat-release" | grep -q "^CentOS Linux release 7" || \ - head "/etc/redhat-release" | grep -q "^Red Hat Enterprise Linux\( Server\)\? release 7" || \ - head "/etc/redhat-release" | grep -q "^Fedora release"; then - virtualenv $1 - else - virtualenv-${PYTHON_VERSION} $1 - fi + # New versions of Python (>3.6) should use venv module + # from stdlib instead of virtualenv package + python3.13 -m venv $1 } # Install pipenv or micropipenv to the separate virtualenv to isolate it @@ -25,7 +21,6 @@ function virtualenv_bin() { # (pip script installer). function install_tool() { echo "---> Installing $1 packaging tool ..." - pip install -U virtualenv VENV_DIR=$HOME/.local/venvs/$1 virtualenv_bin "$VENV_DIR" # First, try to install the tool without --isolated which means that if you @@ -58,20 +53,17 @@ mv /tmp/src/* "$HOME" # set permissions for any installed artifacts fix-permissions /opt/app-root -P -# We have to first upgrade pip to at least 19.3 because: -# * pip < 9 does not support different packages' versions for Python 2/3 -# * pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 wheels -# support platforms like ppc64le, aarch64 or armv7 -echo "---> Upgrading pip to version 19.3.1 ..." -if ! pip install -U "pip==19.3.1"; then - echo "WARNING: Installation of 'pip==19.3.1' failed, trying again from official PyPI with pip --isolated install" - pip install --isolated -U "pip==19.3.1" + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini fi -if [[ ! -z "$UPGRADE_PIP_TO_LATEST" || ! -z "$ENABLE_PIPENV" ]]; then - echo "---> Upgrading pip to latest version ..." +if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then + echo "---> Upgrading pip, setuptools and wheel to latest version ..." if ! pip install -U pip setuptools wheel; then - echo "WARNING: Installation of the latest pip,setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" pip install --isolated -U pip setuptools wheel fi fi @@ -99,8 +91,13 @@ elif [[ -f requirements.txt ]]; then pip install -r requirements.txt fi -if [[ -f setup.py && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then - echo "---> Installing application ..." +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." pip install . fi diff --git a/3.7/s2i/bin/init-wrapper b/3.13-minimal/s2i/bin/init-wrapper similarity index 100% rename from 3.7/s2i/bin/init-wrapper rename to 3.13-minimal/s2i/bin/init-wrapper diff --git a/3.13-minimal/s2i/bin/run b/3.13-minimal/s2i/bin/run new file mode 100755 index 00000000..bc0b3a15 --- /dev/null +++ b/3.13-minimal/s2i/bin/run @@ -0,0 +1,157 @@ +#!/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() { + # Using python and just python here because the script has + # platform-python in its shebang which we don't have in the minimal image + limit_vars=$(python /usr/bin/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)) + # According to http://docs.gunicorn.org/en/stable/design.html#how-many-workers, + # 12 workers should be enough to handle hundreds or thousands requests per second + default=$((default > 12 ? 12 : default)) + echo $default +} + +function maybe_run_in_init_wrapper() { + if [[ -z "$ENABLE_INIT_WRAPPER" ]]; then + exec "$@" + else + exec $STI_SCRIPTS_PATH/init-wrapper "$@" + fi +} + +# Look for gunicorn>=20.1.0 to utilize gunicorn.conf.py +if is_gunicorn_installed && [[ -f "gunicorn.conf.py" ]]; then + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + +APP_HOME=$(readlink -f "${APP_HOME:-.}") +# Change the working directory to APP_HOME +PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" +cd "$APP_HOME" + +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 + fi +fi + +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi +fi + +# Look for 'manage.py' in the current directory +manage_file=./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 not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + +if is_gunicorn_installed; then + setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) + # Look for wsgi module in the current directory + if [[ -z "$APP_MODULE" && -f "./wsgi.py" ]]; then + APP_MODULE=wsgi + elif [[ -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)} + + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" + fi +fi + +if is_django_installed; then + if [[ -f "$manage_file" ]]; then + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." + echo "WARNING: this is NOT a recommended way to run you application in production!" + echo "Consider using gunicorn or some other production web server." + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT + 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/3.13-minimal/s2i/bin/usage b/3.13-minimal/s2i/bin/usage new file mode 100755 index 00000000..77e0aa9f --- /dev/null +++ b/3.13-minimal/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 < -- curl 127.0.0.1:8080 +EOF diff --git a/3.13-minimal/test/__init__.py b/3.13-minimal/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.13-minimal/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/3.5/test/app-home-test-app b/3.13-minimal/test/app-home-test-app similarity index 100% rename from 3.5/test/app-home-test-app rename to 3.13-minimal/test/app-home-test-app diff --git a/3.13-minimal/test/app-module-test-app b/3.13-minimal/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.13-minimal/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/3.8/test/check_imagestreams.py b/3.13-minimal/test/check_imagestreams.py similarity index 100% rename from 3.8/test/check_imagestreams.py rename to 3.13-minimal/test/check_imagestreams.py diff --git a/3.13-minimal/test/conftest.py b/3.13-minimal/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.13-minimal/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.13-minimal/test/django-different-port-test-app b/3.13-minimal/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.13-minimal/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/3.5/test/django-test-app b/3.13-minimal/test/django-test-app similarity index 100% rename from 3.5/test/django-test-app rename to 3.13-minimal/test/django-test-app diff --git a/3.13-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl b/3.13-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl new file mode 120000 index 00000000..f5c5b907 --- /dev/null +++ b/3.13-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/mod_wsgi.Dockerfile.tpl \ No newline at end of file diff --git a/3.13-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl b/3.13-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl new file mode 100644 index 00000000..599866eb --- /dev/null +++ b/3.13-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl @@ -0,0 +1,20 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +ENV UPGRADE_PIP_TO_LATEST=1 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +# Install packages necessary for compiling uwsgi from source +# pkgconfig(python-3.13) is provided by both python3-devel in c9s +# and python3.13-devel in UBI8. +RUN microdnf install -y gcc "pkgconfig(python-3.13)" which +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run diff --git a/3.13-minimal/test/gunicorn-config-different-port-test-app b/3.13-minimal/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.13-minimal/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.13-minimal/test/gunicorn-different-port-test-app b/3.13-minimal/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.13-minimal/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.13-minimal/test/gunicorn-python-configfile-different-port-test-app b/3.13-minimal/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.13-minimal/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/3.8/test/imagestreams b/3.13-minimal/test/imagestreams similarity index 100% rename from 3.8/test/imagestreams rename to 3.13-minimal/test/imagestreams diff --git a/3.5/test/locale-test-app b/3.13-minimal/test/locale-test-app similarity index 100% rename from 3.5/test/locale-test-app rename to 3.13-minimal/test/locale-test-app diff --git a/3.8/test/micropipenv-requirements-test-app b/3.13-minimal/test/micropipenv-requirements-test-app similarity index 100% rename from 3.8/test/micropipenv-requirements-test-app rename to 3.13-minimal/test/micropipenv-requirements-test-app diff --git a/3.5/test/pipenv-test-app/.gitignore b/3.13-minimal/test/micropipenv-test-app/.gitignore similarity index 100% rename from 3.5/test/pipenv-test-app/.gitignore rename to 3.13-minimal/test/micropipenv-test-app/.gitignore diff --git a/3.8/test/micropipenv-test-app/.s2i/environment b/3.13-minimal/test/micropipenv-test-app/.s2i/environment similarity index 100% rename from 3.8/test/micropipenv-test-app/.s2i/environment rename to 3.13-minimal/test/micropipenv-test-app/.s2i/environment diff --git a/3.13-minimal/test/micropipenv-test-app/Pipfile b/3.13-minimal/test/micropipenv-test-app/Pipfile new file mode 100644 index 00000000..fc4d0c80 --- /dev/null +++ b/3.13-minimal/test/micropipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.13" diff --git a/3.13-minimal/test/micropipenv-test-app/Pipfile.lock b/3.13-minimal/test/micropipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..de1ccaf4 --- /dev/null +++ b/3.13-minimal/test/micropipenv-test-app/Pipfile.lock @@ -0,0 +1,211 @@ +{ + "_meta": { + "hash": { + "sha256": "1c042fcc7ce0692ac9ad83e0cc00f01a81991e88b45c33d6edc3e55ac79ae443" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.13" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "index": "pypi", + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "editable": true, + "path": "." + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "pluggy": { + "hashes": [ + "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", + "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" + }, + "pytest": { + "hashes": [ + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" + ], + "index": "pypi", + "version": "==8.3.3" + } + } +} diff --git a/3.5/test/pipenv-test-app/setup.py b/3.13-minimal/test/micropipenv-test-app/setup.py similarity index 100% rename from 3.5/test/pipenv-test-app/setup.py rename to 3.13-minimal/test/micropipenv-test-app/setup.py diff --git a/3.13-minimal/test/micropipenv-test-app/testapp.py b/3.13-minimal/test/micropipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.13-minimal/test/micropipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.5/test/numpy-test-app b/3.13-minimal/test/numpy-test-app similarity index 100% rename from 3.5/test/numpy-test-app rename to 3.13-minimal/test/numpy-test-app diff --git a/3.8/test/pipenv-and-micropipenv-should-fail-test-app b/3.13-minimal/test/pipenv-and-micropipenv-should-fail-test-app similarity index 100% rename from 3.8/test/pipenv-and-micropipenv-should-fail-test-app rename to 3.13-minimal/test/pipenv-and-micropipenv-should-fail-test-app diff --git a/3.7/test/micropipenv-test-app/.gitignore b/3.13-minimal/test/pipenv-test-app/.gitignore similarity index 100% rename from 3.7/test/micropipenv-test-app/.gitignore rename to 3.13-minimal/test/pipenv-test-app/.gitignore diff --git a/3.5/test/pipenv-test-app/.s2i/environment b/3.13-minimal/test/pipenv-test-app/.s2i/environment similarity index 100% rename from 3.5/test/pipenv-test-app/.s2i/environment rename to 3.13-minimal/test/pipenv-test-app/.s2i/environment diff --git a/3.13-minimal/test/pipenv-test-app/Pipfile b/3.13-minimal/test/pipenv-test-app/Pipfile new file mode 100644 index 00000000..fc4d0c80 --- /dev/null +++ b/3.13-minimal/test/pipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.13" diff --git a/3.13-minimal/test/pipenv-test-app/Pipfile.lock b/3.13-minimal/test/pipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..de1ccaf4 --- /dev/null +++ b/3.13-minimal/test/pipenv-test-app/Pipfile.lock @@ -0,0 +1,211 @@ +{ + "_meta": { + "hash": { + "sha256": "1c042fcc7ce0692ac9ad83e0cc00f01a81991e88b45c33d6edc3e55ac79ae443" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.13" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "index": "pypi", + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "editable": true, + "path": "." + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "pluggy": { + "hashes": [ + "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", + "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" + }, + "pytest": { + "hashes": [ + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" + ], + "index": "pypi", + "version": "==8.3.3" + } + } +} diff --git a/3.7/test/micropipenv-test-app/setup.py b/3.13-minimal/test/pipenv-test-app/setup.py similarity index 100% rename from 3.7/test/micropipenv-test-app/setup.py rename to 3.13-minimal/test/pipenv-test-app/setup.py diff --git a/3.13-minimal/test/pipenv-test-app/testapp.py b/3.13-minimal/test/pipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.13-minimal/test/pipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.13-minimal/test/poetry-src-layout-test-app b/3.13-minimal/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.13-minimal/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.13-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.13-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..160e51c4 --- /dev/null +++ b/3.13-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2023.11.14 diff --git a/3.13-minimal/test/pyuwsgi-pipenv-test-app/app.sh b/3.13-minimal/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.13-minimal/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.13-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.13-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.13-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.13-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py b/3.13-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.13-minimal/test/pyuwsgi-pipenv-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() diff --git a/3.13-minimal/test/run b/3.13-minimal/test/run new file mode 100755 index 00000000..8de24fe3 --- /dev/null +++ b/3.13-minimal/test/run @@ -0,0 +1,308 @@ +#!/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. +# +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,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${MINIMAL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) + +# TODO: Make command compatible for Mac users +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile_minimal +" + +if [[ -z $VERSION ]]; then + echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 + exit 1 +fi + +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} + +. test/test-lib.sh + +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 ..." + ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + return 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() { + 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 +} +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + info "Waiting for application container to start $CONTAINER_ARGS ..." + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && return 0 + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return 1 +} + +test_s2i_usage() { + info "Testing 's2i usage' ..." + ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage ..." + docker run --rm ${IMAGE_NAME} &>/dev/null +} + +test_scl_usage() { + local run_cmd="$1" + local expected="$2" + local cid_file="$3" + + info "Testing command availability in the image" + 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="$CID_FILE_DIR"/"$(mktemp -u -p . --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 + # Some test apps have tests in their startup code so we have to check + # that the container starts at all + ct_check_testcase_result $? + + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? + test_connection + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") +} + +test_from_dockerfile(){ + info "Test from Dockerfile" + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? +} + +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" + + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. + + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? + + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? + else + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." + fi + +} + +test_application_with_user() { + # test application with random user + CONTAINER_ARGS="--user 12345" test_application + +} + +test_application_enable_init_wrapper() { + # test application with init wrapper + CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue + + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" + + cleanup ${app} +done + +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.13-minimal/test/run-openshift-pytest b/3.13-minimal/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.13-minimal/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.13-minimal/test/run-pytest b/3.13-minimal/test/run-pytest new file mode 100755 index 00000000..9810fe89 --- /dev/null +++ b/3.13-minimal/test/run-pytest @@ -0,0 +1,16 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +if python3 -c 'import sys; sys.exit(0 if sys.version_info < (3,13) else 1)'; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.13-minimal/test/setup-cfg-test-app b/3.13-minimal/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.13-minimal/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/3.5/test/setup-requirements-test-app b/3.13-minimal/test/setup-requirements-test-app similarity index 100% rename from 3.5/test/setup-requirements-test-app rename to 3.13-minimal/test/setup-requirements-test-app diff --git a/3.5/test/setup-test-app b/3.13-minimal/test/setup-test-app similarity index 100% rename from 3.5/test/setup-test-app rename to 3.13-minimal/test/setup-test-app diff --git a/3.13-minimal/test/standalone-custom-pypi-index-test-app b/3.13-minimal/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.13-minimal/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/3.5/test/standalone-test-app b/3.13-minimal/test/standalone-test-app similarity index 100% rename from 3.5/test/standalone-test-app rename to 3.13-minimal/test/standalone-test-app diff --git a/3.5/test/test-lib.sh b/3.13-minimal/test/test-lib.sh similarity index 100% rename from 3.5/test/test-lib.sh rename to 3.13-minimal/test/test-lib.sh diff --git a/3.13-minimal/test/test_container_application.py b/3.13-minimal/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.13-minimal/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.13-minimal/test/test_container_basics.py b/3.13-minimal/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.13-minimal/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.13-minimal/test/test_ocp_deploy_templates.py b/3.13-minimal/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.13-minimal/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.13-minimal/test/test_ocp_helm_python_django_app.py b/3.13-minimal/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.13-minimal/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.13-minimal/test/test_ocp_helm_python_django_psql_persistent.py b/3.13-minimal/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.13-minimal/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.13-minimal/test/test_ocp_helm_python_imagestreams.py b/3.13-minimal/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.13-minimal/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.13-minimal/test/test_ocp_imagestreams_quickstart.py b/3.13-minimal/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.13-minimal/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.13-minimal/test/test_ocp_python_ex_standalone.py b/3.13-minimal/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.13-minimal/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.13-minimal/test/test_ocp_python_ex_template.py b/3.13-minimal/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.13-minimal/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file diff --git a/3.13-minimal/test/uwsgi-test-app b/3.13-minimal/test/uwsgi-test-app new file mode 120000 index 00000000..c3ccf241 --- /dev/null +++ b/3.13-minimal/test/uwsgi-test-app @@ -0,0 +1 @@ +../../examples/uwsgi-test-app \ No newline at end of file diff --git a/3.13/Dockerfile.c10s b/3.13/Dockerfile.c10s new file mode 100644 index 00000000..87196fe3 --- /dev/null +++ b/3.13/Dockerfile.c10s @@ -0,0 +1,89 @@ +# This image provides a Python 3.13 environment you can use to run your Python +# applications. +FROM quay.io/sclorg/s2i-base-c10s:c10s + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.13 \ + 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 \ + ARCH=x86_64 + +ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.13" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python313,python-313,rh-python313" \ + com.redhat.component="$NAME" \ + name="sclorg/python-313-c10s" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.13/test/setup-test-app/ $FGC/$NAME python-sample-app" \ + maintainer="SoftwareCollections.org " + +RUN INSTALL_PKGS="python3.13 python3.13-devel python3.13-pip nss_wrapper-libs httpd \ + httpd-devel mod_ssl mod_auth_gssapi mod_ldap mod_session \ + gcc-gfortran libffi-devel libtool-ltdl krb5-devel" && \ + yum -y install --setopt=install_weak_deps=0 epel-release && \ + yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + yum -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.13/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.13/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.13/root/opt/wheels /opt/wheels +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 python3.13 -m venv ${APP_ROOT} && \ +# Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ +# We have to upgrade pip to a newer verison because \ +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ +# support platforms like ppc64le, aarch64 or armv7 \ +# We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ +# because it's tested better then whatever version from PyPI and contains useful patches. \ +# We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ +# to reinstall itself in the next build phases in the assemble script if user wants the latest version \ +${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +rm -r /opt/wheels && \ +chown -R 1001:0 ${APP_ROOT} && \ +fix-permissions ${APP_ROOT} -P && \ +# The following echo adds the unset command for the variables set below to the \ +# venv activation script. This prevents the virtual environment from being \ +# activated multiple times and also every time the prompt is rendered. \ +echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.7/Dockerfile.fedora b/3.13/Dockerfile.fedora similarity index 51% rename from 3.7/Dockerfile.fedora rename to 3.13/Dockerfile.fedora index 48efaa23..c8b9bbdd 100644 --- a/3.7/Dockerfile.fedora +++ b/3.13/Dockerfile.fedora @@ -1,10 +1,10 @@ -# This image provides a Python 3.7 environment you can use to run your Python +# This image provides a Python 3.13 environment you can use to run your Python # applications. -FROM registry.fedoraproject.org/f31/s2i-base:latest +FROM quay.io/fedora/s2i-base:42 EXPOSE 8080 -ENV PYTHON_VERSION=3.7 \ +ENV PYTHON_VERSION=3.13 \ PATH=$HOME/.local/bin/:$PATH \ PYTHONUNBUFFERED=1 \ PYTHONIOENCODING=UTF-8 \ @@ -12,9 +12,7 @@ ENV PYTHON_VERSION=3.7 \ LANG=en_US.UTF-8 \ PIP_NO_CACHE_DIR=off - ENV NAME=python3 \ - VERSION=0 \ ARCH=x86_64 ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ @@ -29,40 +27,57 @@ on most platforms." LABEL summary="$SUMMARY" \ description="$DESCRIPTION" \ io.k8s.description="$DESCRIPTION" \ - io.k8s.display-name="Python 3.7" \ + io.k8s.display-name="Python 3.13" \ io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python37,python-37,rh-python37" \ + io.openshift.tags="builder,python,python313,python-313,rh-python313" \ com.redhat.component="$NAME" \ - name="$FGC/$NAME" \ - version="$VERSION" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.7/test/setup-test-app/ $FGC/$NAME python-sample-app" \ + name="fedora/$NAME-313" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.13/test/setup-test-app/ quay.io/fedora/$NAME-313 python-sample-app" \ maintainer="SoftwareCollections.org " -RUN INSTALL_PKGS="python3 python3-devel python3-setuptools python3-pip nss_wrapper \ +RUN INSTALL_PKGS="python3 python3-devel python3-setuptools python3-pip nss_wrapper-libs \ httpd httpd-devel atlas-devel gcc-gfortran libffi-devel \ - libtool-ltdl enchant redhat-rpm-config" && \ + libtool-ltdl enchant redhat-rpm-config krb5-devel" && \ dnf -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ dnf -y clean all --enablerepo='*' # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. -COPY ./s2i/bin/ $STI_SCRIPTS_PATH +COPY 3.13/s2i/bin/ $STI_SCRIPTS_PATH # Copy extra files to the image. -COPY ./root/ / +COPY 3.13/root/ / +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.13/root/opt/wheels /opt/wheels # - Create a Python virtual environment for use by any application to avoid # potential conflicts with Python packages preinstalled in the main Python # installation. # - 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 python3.7 -m venv ${APP_ROOT} && \ +RUN python3.13 -m venv ${APP_ROOT} && \ +# Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ +# We have to upgrade pip to a newer verison because \ +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ +# support platforms like ppc64le, aarch64 or armv7 \ +# We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ +# because it's tested better then whatever version from PyPI and contains useful patches. \ +# We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ +# to reinstall itself in the next build phases in the assemble script if user wants the latest version \ +${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +rm -r /opt/wheels && \ chown -R 1001:0 ${APP_ROOT} && \ -fix-permissions ${APP_ROOT} -P - -# For Fedora scl_enable isn't sourced automatically in s2i-core -# so virtualenv needs to be activated this way +fix-permissions ${APP_ROOT} -P && \ +# The following echo adds the unset command for the variables set below to the \ +# venv activation script. This prevents the virtual environment from being \ +# activated multiple times and also every time the prompt is rendered. \ +echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells ENV BASH_ENV="${APP_ROOT}/bin/activate" \ ENV="${APP_ROOT}/bin/activate" \ PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" diff --git a/3.8/README.md b/3.13/README.md similarity index 59% rename from 3.8/README.md rename to 3.13/README.md index 14b66a70..7bd4ddfd 100644 --- a/3.8/README.md +++ b/3.13/README.md @@ -1,11 +1,11 @@ -Python 3.8 container image -=================== - -This container image includes Python 3.8 as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.8 applications. -Users can choose between RHEL and CentOS based builder images. -The RHEL images are available in the [Red Hat Container Catalog](https://access.redhat.com/containers/), -the CentOS images are available on [Docker Hub](https://hub.docker.com/r/centos/), -and the Fedora images are available in [Fedora Registry](https://registry.fedoraproject.org/). +Python 3.13 container image +========================= + +This container image includes Python 3.13 as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.13 applications. +Users can choose between RHEL, CentOS Stream, and Fedora based builder images. +The RHEL images are available in the [Red Hat Container Catalog](https://catalog.redhat.com/software/containers/explore), +CentOS Stream images are available on [Quay.io](https://quay.io/organization/sclorg), +and the Fedora images are available on [Quay.io](https://quay.io/organization/fedora). The resulting image can be run using [podman](https://github.com/containers/libpod) or [docker](http://docker.io). @@ -14,8 +14,8 @@ Note: while the examples in this README are calling `podman`, you can replace an Description ----------- -Python 3.8 available as container is a base platform for -building and running various Python 3.8 applications and frameworks. +Python 3.13 available as container is a base platform for +building and running various Python 3.13 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, @@ -29,28 +29,132 @@ modules for their web applications. There is no guarantee for any specific npm o version, that is included in the image; those versions can be changed anytime and the nodejs itself is included just to make the npm work. -Usage ---------------------- +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Usage in Openshift +------------------ -For this, we will assume that you are using the supported image, available via `python:3.8` imagestream tag in Openshift. -Building a simple [python-sample-app](https://github.com/sclorg/s2i-python-container/tree/master/3.8/test/setup-test-app) application +For this, we will assume that you are using one of the supported images available via imagestream tags in Openshift, eg. `python:3.13-ubi9` +Building a simple [python-sample-app](https://github.com/sclorg/django-ex.git) application in Openshift can be achieved with the following step: ``` - oc new-app python:3.8~https://github.com/sclorg/s2i-python-container.git --context-dir=3.8/test/setup-test-app/ + oc new-app python:3.13-ubi9~https://github.com/sclorg/django-ex.git ``` -The same application can also be built using the standalone [S2I](https://github.com/openshift/source-to-image) application on systems that have it available: +**Accessing the application:** +``` +$ oc get pods +$ oc exec -- curl 127.0.0.1:8080 +``` - ``` - $ s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.8/test/setup-test-app/ python-sample-app - ``` +Source-to-Image framework and scripts +------------------------------------- +This image supports the [Source-to-Image](https://docs.openshift.com/container-platform/4.14/openshift_images/create-images.html#images-create-s2i_create-images) +(S2I) strategy in OpenShift. The Source-to-Image is an OpenShift framework +which makes it easy to write images that take application source code as +an input, use a builder image like this Python container image, and produce +a new image that runs the assembled application as an output. + +To support the Source-to-Image framework, important scripts are included in the builder image: + +* The `/usr/libexec/s2i/assemble` script inside the image is run to produce a new image with the application artifacts. +The script takes sources of a given application and places them into appropriate directories inside the image. +It utilizes some common patterns in Perl application development (see the **Environment variables** section below). +* The `/usr/libexec/s2i/run` script is set as the default command in the resulting container image (the new image with the application artifacts). +It runs your application according to settings in `APP_MODULE`, `APP_FILE` or `APP_SCRIPT` environment variables or it tries to detect the best +way automatically. + +Building an application using a Dockerfile +------------------------------------------ +Compared to the Source-to-Image strategy, using a Dockerfile is a more +flexible way to build a Python container image with an application. +Use a Dockerfile when Source-to-Image is not sufficiently flexible for you or +when you build the image outside of the OpenShift environment. + +To use the Python image in a Dockerfile, follow these steps: +#### 1. Pull a base builder image to build on -Where `` is the s2i-python image you [downloaded from RHEL, Centos or Fedora registry](../README.md#Download) or [built](../README.md#Build) from these sources. For example ubi8/python-36, centos/python-36-centos7 or f31/python3. +``` +podman pull quay.io/fedora/python-313 +``` -**Accessing the application:** +#### 2. Pull and application code + +An example application available at https://github.com/sclorg/django-ex.git is used here. Feel free to clone the repository for further experiments. +You can also take a look at code examples in s2i-python-container repository: https://github.com/sclorg/s2i-python-container/tree/master/examples + +``` +git clone https://github.com/sclorg/django-ex.git app-src ``` -$ curl 127.0.0.1:8080 + +#### 3. Prepare an application inside a container + +This step usually consists of at least these parts: + +* putting the application source into the container +* installing the dependencies +* setting the default command in the resulting image + +For all these three parts, users can either setup all manually and use commands `python` and `pip` explicitly in the Dockerfile, +or users can use the Source-to-Image scripts inside the image. + +The manual way comes with the highest level of flexibility but requires you to know how to work +with modules or software collections manually, how to setup virtual environment with the right version +of Python and many more. On the other hand, using Source-to-Image scripts makes your Dockerfile +prepared for a future flawless switch to a newer or different platform. + +To use the Source-to-Image scripts and build an image using a Dockerfile, create a Dockerfile with this content: + +``` +FROM quay.io/fedora/python-313 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you decide not to use the Source-to-Image scripts, you will need to manually tailor the Dockerfile to your application and its needs. +Example Dockerfile for a simple Django application: + +``` +FROM quay.io/fedora/python-313 + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 +``` + +#### 4. Build a new image from a Dockerfile prepared in the previous step + +``` +podman build -t python-app . +``` + +#### 5. Run the resulting image with final application + +``` +podman run -d python-app ``` Environment variables @@ -86,7 +190,7 @@ file inside your source code repository. 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/sclorg/s2i-python-container/tree/master/3.8/test/setup-test-app). + [setup-test-app](https://github.com/sclorg/s2i-python-container/tree/master/3.13/test/setup-test-app). * **APP_HOME** @@ -115,9 +219,9 @@ file inside your source code repository. 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_SETUP_PY_PROCESSING** +* **DISABLE_SETUP_PY_PROCESSING** / **DISABLE_PYPROJECT_TOML_PROCESSING** - Set this to a non-empty value to skip processing of setup.py script if you + Set this to a non-empty value to skip processing of setup.{py,cfg} or pyproject.toml script if you use `-e .` in requirements.txt to trigger its processing or you don't want your application to be installed into site-packages directory. @@ -127,6 +231,7 @@ file inside your source code repository. the higher-level Python packaging tool, to manage dependencies of the application. This should be used only if your project contains properly formated Pipfile and Pipfile.lock. + * **PIN_PIPENV_VERSION** Set this variable together with `ENABLE_PIPENV` to use a specific version of Pipenv. @@ -158,12 +263,18 @@ file inside your source code repository. the custom index, the container will try to install/update them from upstream PyPI afterwards. +* **PORT** + + HTTP(S) port your application should listen on. The default is 8080. + `PORT` is used only for Django development server and for Gunicorn + with the default configutation (no `APP_CONFIG` or `GUNICORN_CMD_ARGS` specified). + * **UPGRADE_PIP_TO_LATEST** Set this variable to a non-empty value to have the 'pip' program and related python packages (setuptools and wheel) 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. + before any Python packages are installed. If not set, the container will use + the stable pip version this container was built with, taken from a recent Fedora release. * **WEB_CONCURRENCY** @@ -196,7 +307,7 @@ However, if these files exist they will affect the behavior of the build process Configures various aspects of the project, including installation of dependencies, as documented - [here](https://packaging.python.org/en/latest/distributing.html#setup-py). + [here](https://packaging.python.org/guides/distributing-packages-using-setuptools/?highlight=distributing#setup-py). For most projects, it is sufficient to simply use `requirements.txt` or `Pipfile`. Set `DISABLE_SETUP_PY_PROCESSING` environment variable to true in order to skip processing of this file. @@ -221,6 +332,9 @@ following ways, in precedence order: If you have both Django and Gunicorn in your requirements, your Django project will automatically be served using Gunicorn. + The default setting for Gunicorn (`--bind=0.0.0.0:$PORT --access-logfile=-`) is applied + only if both `$APP_CONFIG` and `$GUNICORN_CMD_ARGS` are not defined. + * **Django development server** If you have Django in your requirements but don't have Gunicorn, then your @@ -244,7 +358,7 @@ following ways, in precedence order: application. Hot deploy ---------------------- +---------- If you are using Django, hot deploy will work out of the box. @@ -255,7 +369,7 @@ 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 podman's (or docker's) -[exec](https://github.com/containers/libpod/blob/master/docs/podman-exec.1.md) command: +[exec](https://github.com/containers/podman/blob/main/docs/source/markdown/podman-exec.1.md) command: ``` podman exec -it /bin/bash @@ -269,5 +383,6 @@ See also -------- Dockerfile and other sources are available on https://github.com/sclorg/s2i-python-container. In that repository you also can find another versions of Python environment Dockerfiles. -Dockerfile for CentOS is called `Dockerfile`, Dockerfile for RHEL7 is called `Dockerfile.rhel7`, -for RHEL8 it's `Dockerfile.rhel8` and the Fedora Dockerfile is called `Dockerfile.fedora`. +Dockerfile for RHEL8 is called `Dockerfile.rhel8`, for RHEL9 it's `Dockerfile.rhel9`, +for CentOS Stream 9 it's `Dockerfile.c9s`, for CentOS Stream 10 it's `Dockerfile.c10s`, +and the Fedora Dockerfile is called `Dockerfile.fedora`. diff --git a/3.4/root/opt/app-root/etc/generate_container_user b/3.13/root/opt/app-root/etc/generate_container_user similarity index 90% rename from 3.4/root/opt/app-root/etc/generate_container_user rename to 3.13/root/opt/app-root/etc/generate_container_user index 43ee7ecb..a7fd74d6 100644 --- a/3.4/root/opt/app-root/etc/generate_container_user +++ b/3.13/root/opt/app-root/etc/generate_container_user @@ -9,7 +9,7 @@ if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"1001" ]; then 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 + echo "default:x:${USER_ID}:${GROUP_ID}:Default Application User:${HOME}:/bin/bash" >> $NSS_WRAPPER_PASSWD export NSS_WRAPPER_PASSWD export NSS_WRAPPER_GROUP diff --git a/3.13/root/opt/wheels b/3.13/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.13/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.13/s2i/bin/assemble b/3.13/s2i/bin/assemble new file mode 100755 index 00000000..f415fe30 --- /dev/null +++ b/3.13/s2i/bin/assemble @@ -0,0 +1,134 @@ +#!/bin/bash + +function is_django_installed() { + python -c "import django" &>/dev/null +} + +function should_collectstatic() { + is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] +} + +function virtualenv_bin() { + # New versions of Python (>3.6) should use venv module + # from stdlib instead of virtualenv package + python3.13 -m venv $1 +} + +# Install pipenv or micropipenv to the separate virtualenv to isolate it +# from system Python packages and packages in the main +# virtualenv. Executable is simlinked into ~/.local/bin +# to be accessible. This approach is inspired by pipsi +# (pip script installer). +function install_tool() { + echo "---> Installing $1 packaging tool ..." + VENV_DIR=$HOME/.local/venvs/$1 + virtualenv_bin "$VENV_DIR" + # First, try to install the tool without --isolated which means that if you + # have your own PyPI mirror, it will take it from there. If this try fails, try it + # again with --isolated which ignores external pip settings (env vars, config file) + # and installs the tool from PyPI (needs internet connetion). + # $1$2 combines package name with [extras] or version specifier if is defined as $2``` + if ! $VENV_DIR/bin/pip install -U $1$2; then + echo "WARNING: Installation of $1 failed, trying again from official PyPI with pip --isolated install" + $VENV_DIR/bin/pip install --isolated -U $1$2 # Combines package name with [extras] or version specifier if is defined as $2``` + fi + mkdir -p $HOME/.local/bin + ln -s $VENV_DIR/bin/$1 $HOME/.local/bin/$1 +} + +set -e + +# First of all, check that we don't have disallowed combination of ENVs +if [[ ! -z "$ENABLE_PIPENV" && ! -z "$ENABLE_MICROPIPENV" ]]; then + echo "ERROR: Pipenv and micropipenv cannot be enabled at the same time!" + # podman/buildah does not relay this exit code but it will be fixed hopefuly + # https://github.com/containers/buildah/issues/2305 + exit 3 +fi + +shopt -s dotglob +echo "---> Installing application source ..." +mv /tmp/src/* "$HOME" + +# set permissions for any installed artifacts +fix-permissions /opt/app-root -P + + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini +fi + +if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then + echo "---> Upgrading pip, setuptools and wheel to latest version ..." + if ! pip install -U pip setuptools wheel; then + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + pip install --isolated -U pip setuptools wheel + fi +fi + +if [[ ! -z "$ENABLE_PIPENV" ]]; then + if [[ ! -z "$PIN_PIPENV_VERSION" ]]; then + # Add == as a prefix to pipenv version, if defined + PIN_PIPENV_VERSION="==$PIN_PIPENV_VERSION" + fi + install_tool "pipenv" "$PIN_PIPENV_VERSION" + echo "---> Installing dependencies via pipenv ..." + if [[ -f Pipfile ]]; then + pipenv install --deploy + elif [[ -f requirements.txt ]]; then + pipenv install -r requirements.txt + fi + # pipenv check +elif [[ ! -z "$ENABLE_MICROPIPENV" ]]; then + install_tool "micropipenv" "[toml]" + echo "---> Installing dependencies via micropipenv ..." + # micropipenv detects Pipfile.lock and requirements.txt in this order + micropipenv install --deploy +elif [[ -f requirements.txt ]]; then + echo "---> Installing dependencies ..." + pip install -r requirements.txt +fi + +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." + pip install . +fi + +if should_collectstatic; then + ( + echo "---> Collecting Django static files ..." + + APP_HOME=$(readlink -f "${APP_HOME:-.}") + # Change the working directory to APP_HOME + PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" + cd "$APP_HOME" + + # Look for 'manage.py' in the current directory + manage_file=./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 -P diff --git a/3.8/s2i/bin/init-wrapper b/3.13/s2i/bin/init-wrapper similarity index 100% rename from 3.8/s2i/bin/init-wrapper rename to 3.13/s2i/bin/init-wrapper diff --git a/2.7/s2i/bin/run b/3.13/s2i/bin/run similarity index 54% rename from 2.7/s2i/bin/run rename to 3.13/s2i/bin/run index ee1e1d3b..7003f038 100755 --- a/2.7/s2i/bin/run +++ b/3.13/s2i/bin/run @@ -43,30 +43,54 @@ 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 + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + APP_HOME=$(readlink -f "${APP_HOME:-.}") # Change the working directory to APP_HOME PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" cd "$APP_HOME" -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" +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 fi - maybe_run_in_init_wrapper "$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) ..." - maybe_run_in_init_wrapper python "$APP_FILE" -else - test -n "$app_file_check" && (>&2 echo "ERROR: file '$app_file_check' not found.") && exit 1 +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi fi # Look for 'manage.py' in the current directory @@ -82,6 +106,11 @@ if should_migrate; then fi fi +# If not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + if is_gunicorn_installed; then setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) # Look for wsgi module in the current directory @@ -94,17 +123,27 @@ if is_gunicorn_installed; then 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" + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" fi fi if is_django_installed; then if [[ -f "$manage_file" ]]; then - echo "---> Serving application with 'manage.py runserver' ..." + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." echo "WARNING: this is NOT a recommended way to run you application in production!" echo "Consider using gunicorn or some other production web server." - maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:8080 + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT else echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." echo "Skipped 'python manage.py runserver'." diff --git a/3.13/s2i/bin/usage b/3.13/s2i/bin/usage new file mode 100755 index 00000000..7ee134dd --- /dev/null +++ b/3.13/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 < -- curl 127.0.0.1:8080 +EOF diff --git a/3.13/test/__init__.py b/3.13/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.13/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/3.7/test/app-home-test-app b/3.13/test/app-home-test-app similarity index 100% rename from 3.7/test/app-home-test-app rename to 3.13/test/app-home-test-app diff --git a/3.13/test/app-module-test-app b/3.13/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.13/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/3.13/test/check_imagestreams.py b/3.13/test/check_imagestreams.py new file mode 120000 index 00000000..56bb2be7 --- /dev/null +++ b/3.13/test/check_imagestreams.py @@ -0,0 +1 @@ +../../common/check_imagestreams.py \ No newline at end of file diff --git a/3.13/test/conftest.py b/3.13/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.13/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.13/test/django-different-port-test-app b/3.13/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.13/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/3.7/test/django-test-app b/3.13/test/django-test-app similarity index 100% rename from 3.7/test/django-test-app rename to 3.13/test/django-test-app diff --git a/3.13/test/from-dockerfile/Dockerfile.tpl b/3.13/test/from-dockerfile/Dockerfile.tpl new file mode 120000 index 00000000..ba88ccc4 --- /dev/null +++ b/3.13/test/from-dockerfile/Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/Dockerfile.tpl \ No newline at end of file diff --git a/3.13/test/from-dockerfile/Dockerfile_no_s2i.tpl b/3.13/test/from-dockerfile/Dockerfile_no_s2i.tpl new file mode 100644 index 00000000..da2f43ff --- /dev/null +++ b/3.13/test/from-dockerfile/Dockerfile_no_s2i.tpl @@ -0,0 +1,16 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 diff --git a/3.13/test/gunicorn-config-different-port-test-app b/3.13/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.13/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.13/test/gunicorn-different-port-test-app b/3.13/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.13/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.13/test/gunicorn-python-configfile-different-port-test-app b/3.13/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.13/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/3.13/test/imagestreams b/3.13/test/imagestreams new file mode 120000 index 00000000..7a0aee9c --- /dev/null +++ b/3.13/test/imagestreams @@ -0,0 +1 @@ +../../imagestreams \ No newline at end of file diff --git a/3.7/test/locale-test-app b/3.13/test/locale-test-app similarity index 100% rename from 3.7/test/locale-test-app rename to 3.13/test/locale-test-app diff --git a/3.13/test/micropipenv-requirements-test-app b/3.13/test/micropipenv-requirements-test-app new file mode 120000 index 00000000..cb2ddcf0 --- /dev/null +++ b/3.13/test/micropipenv-requirements-test-app @@ -0,0 +1 @@ +../../examples/micropipenv-requirements-test-app \ No newline at end of file diff --git a/3.7/test/pipenv-test-app/.gitignore b/3.13/test/micropipenv-test-app/.gitignore similarity index 100% rename from 3.7/test/pipenv-test-app/.gitignore rename to 3.13/test/micropipenv-test-app/.gitignore diff --git a/3.13/test/micropipenv-test-app/.s2i/environment b/3.13/test/micropipenv-test-app/.s2i/environment new file mode 100644 index 00000000..29efed4a --- /dev/null +++ b/3.13/test/micropipenv-test-app/.s2i/environment @@ -0,0 +1,5 @@ +ENABLE_MICROPIPENV=true +DISABLE_SETUP_PY_PROCESSING=true +# This tests second try to install micropipenv with --isolated +# because the first one won't work with following setting +PIP_INDEX_URL=https://example.com/ diff --git a/3.13/test/micropipenv-test-app/Pipfile b/3.13/test/micropipenv-test-app/Pipfile new file mode 100644 index 00000000..fc4d0c80 --- /dev/null +++ b/3.13/test/micropipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.13" diff --git a/3.13/test/micropipenv-test-app/Pipfile.lock b/3.13/test/micropipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..de1ccaf4 --- /dev/null +++ b/3.13/test/micropipenv-test-app/Pipfile.lock @@ -0,0 +1,211 @@ +{ + "_meta": { + "hash": { + "sha256": "1c042fcc7ce0692ac9ad83e0cc00f01a81991e88b45c33d6edc3e55ac79ae443" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.13" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "index": "pypi", + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "editable": true, + "path": "." + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "pluggy": { + "hashes": [ + "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", + "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" + }, + "pytest": { + "hashes": [ + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" + ], + "index": "pypi", + "version": "==8.3.3" + } + } +} diff --git a/3.7/test/pipenv-test-app/setup.py b/3.13/test/micropipenv-test-app/setup.py similarity index 100% rename from 3.7/test/pipenv-test-app/setup.py rename to 3.13/test/micropipenv-test-app/setup.py diff --git a/3.13/test/micropipenv-test-app/testapp.py b/3.13/test/micropipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.13/test/micropipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.4/test/mod-wsgi-test-app b/3.13/test/mod-wsgi-test-app similarity index 100% rename from 3.4/test/mod-wsgi-test-app rename to 3.13/test/mod-wsgi-test-app diff --git a/3.4/test/npm-virtualenv-uwsgi-test-app b/3.13/test/npm-virtualenv-uwsgi-test-app similarity index 100% rename from 3.4/test/npm-virtualenv-uwsgi-test-app rename to 3.13/test/npm-virtualenv-uwsgi-test-app diff --git a/3.7/test/numpy-test-app b/3.13/test/numpy-test-app similarity index 100% rename from 3.7/test/numpy-test-app rename to 3.13/test/numpy-test-app diff --git a/3.13/test/pin-pipenv-version-test-app/.s2i/environment b/3.13/test/pin-pipenv-version-test-app/.s2i/environment new file mode 100644 index 00000000..160e51c4 --- /dev/null +++ b/3.13/test/pin-pipenv-version-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2023.11.14 diff --git a/3.13/test/pin-pipenv-version-test-app/app.sh b/3.13/test/pin-pipenv-version-test-app/app.sh new file mode 100755 index 00000000..4334c760 --- /dev/null +++ b/3.13/test/pin-pipenv-version-test-app/app.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.13/test/pin-pipenv-version-test-app/requirements.txt b/3.13/test/pin-pipenv-version-test-app/requirements.txt new file mode 100644 index 00000000..66d42051 --- /dev/null +++ b/3.13/test/pin-pipenv-version-test-app/requirements.txt @@ -0,0 +1,2 @@ +uWSGI +Flask diff --git a/3.13/test/pin-pipenv-version-test-app/wsgi.py b/3.13/test/pin-pipenv-version-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.13/test/pin-pipenv-version-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() diff --git a/3.13/test/pipenv-and-micropipenv-should-fail-test-app b/3.13/test/pipenv-and-micropipenv-should-fail-test-app new file mode 120000 index 00000000..cf830645 --- /dev/null +++ b/3.13/test/pipenv-and-micropipenv-should-fail-test-app @@ -0,0 +1 @@ +../../src/test/pipenv-and-micropipenv-should-fail-test-app \ No newline at end of file diff --git a/3.8/test/micropipenv-test-app/.gitignore b/3.13/test/pipenv-test-app/.gitignore similarity index 100% rename from 3.8/test/micropipenv-test-app/.gitignore rename to 3.13/test/pipenv-test-app/.gitignore diff --git a/3.7/test/pipenv-test-app/.s2i/environment b/3.13/test/pipenv-test-app/.s2i/environment similarity index 100% rename from 3.7/test/pipenv-test-app/.s2i/environment rename to 3.13/test/pipenv-test-app/.s2i/environment diff --git a/3.13/test/pipenv-test-app/Pipfile b/3.13/test/pipenv-test-app/Pipfile new file mode 100644 index 00000000..fc4d0c80 --- /dev/null +++ b/3.13/test/pipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.13" diff --git a/3.13/test/pipenv-test-app/Pipfile.lock b/3.13/test/pipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..de1ccaf4 --- /dev/null +++ b/3.13/test/pipenv-test-app/Pipfile.lock @@ -0,0 +1,211 @@ +{ + "_meta": { + "hash": { + "sha256": "1c042fcc7ce0692ac9ad83e0cc00f01a81991e88b45c33d6edc3e55ac79ae443" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.13" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "index": "pypi", + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "editable": true, + "path": "." + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "pluggy": { + "hashes": [ + "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", + "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" + }, + "pytest": { + "hashes": [ + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" + ], + "index": "pypi", + "version": "==8.3.3" + } + } +} diff --git a/3.8/test/micropipenv-test-app/setup.py b/3.13/test/pipenv-test-app/setup.py similarity index 100% rename from 3.8/test/micropipenv-test-app/setup.py rename to 3.13/test/pipenv-test-app/setup.py diff --git a/3.13/test/pipenv-test-app/testapp.py b/3.13/test/pipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.13/test/pipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.13/test/poetry-src-layout-test-app b/3.13/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.13/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.13/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.13/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..160e51c4 --- /dev/null +++ b/3.13/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2023.11.14 diff --git a/3.13/test/pyuwsgi-pipenv-test-app/app.sh b/3.13/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.13/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.13/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.13/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.13/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.13/test/pyuwsgi-pipenv-test-app/wsgi.py b/3.13/test/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.13/test/pyuwsgi-pipenv-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() diff --git a/3.13/test/run b/3.13/test/run new file mode 100755 index 00000000..134639eb --- /dev/null +++ b/3.13/test/run @@ -0,0 +1,312 @@ +#!/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. +# +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,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${FULL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) + +# TODO: Make command compatible for Mac users +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile +" + +if [[ -z $VERSION ]]; then + echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 + exit 1 +fi + +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} + +. test/test-lib.sh + +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 ..." + ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + return 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() { + 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 +} +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + info "Waiting for application container to start $CONTAINER_ARGS ..." + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && return 0 + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return 1 +} + +test_s2i_usage() { + info "Testing 's2i usage' ..." + ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage ..." + docker run --rm ${IMAGE_NAME} &>/dev/null +} + +test_scl_usage() { + local run_cmd="$1" + local expected="$2" + local cid_file="$3" + + info "Testing command availability in the image" + 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="$CID_FILE_DIR"/"$(mktemp -u -p . --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 + # Some test apps have tests in their startup code so we have to check + # that the container starts at all + ct_check_testcase_result $? + + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? + test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" + ct_check_testcase_result $? + test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" + ct_check_testcase_result $? + test_connection + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") +} + +test_from_dockerfile(){ + info "Test from Dockerfile" + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? +} + +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" + + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. + + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? + + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? + else + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." + fi + +} + +test_application_with_user() { + # test application with random user + CONTAINER_ARGS="--user 12345" test_application + +} + +test_application_enable_init_wrapper() { + # test application with init wrapper + CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue + + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" + + cleanup ${app} +done + +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.13/test/run-openshift-pytest b/3.13/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.13/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.13/test/run-pytest b/3.13/test/run-pytest new file mode 100755 index 00000000..9810fe89 --- /dev/null +++ b/3.13/test/run-pytest @@ -0,0 +1,16 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +if python3 -c 'import sys; sys.exit(0 if sys.version_info < (3,13) else 1)'; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.13/test/setup-cfg-test-app b/3.13/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.13/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/3.7/test/setup-requirements-test-app b/3.13/test/setup-requirements-test-app similarity index 100% rename from 3.7/test/setup-requirements-test-app rename to 3.13/test/setup-requirements-test-app diff --git a/3.7/test/setup-test-app b/3.13/test/setup-test-app similarity index 100% rename from 3.7/test/setup-test-app rename to 3.13/test/setup-test-app diff --git a/3.13/test/standalone-custom-pypi-index-test-app b/3.13/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.13/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/3.7/test/standalone-test-app b/3.13/test/standalone-test-app similarity index 100% rename from 3.7/test/standalone-test-app rename to 3.13/test/standalone-test-app diff --git a/3.7/test/test-lib.sh b/3.13/test/test-lib.sh similarity index 100% rename from 3.7/test/test-lib.sh rename to 3.13/test/test-lib.sh diff --git a/3.13/test/test_container_application.py b/3.13/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.13/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.13/test/test_container_basics.py b/3.13/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.13/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.13/test/test_ocp_deploy_templates.py b/3.13/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.13/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.13/test/test_ocp_helm_python_django_app.py b/3.13/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.13/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.13/test/test_ocp_helm_python_django_psql_persistent.py b/3.13/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.13/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.13/test/test_ocp_helm_python_imagestreams.py b/3.13/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.13/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.13/test/test_ocp_imagestreams_quickstart.py b/3.13/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.13/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.13/test/test_ocp_python_ex_standalone.py b/3.13/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.13/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.13/test/test_ocp_python_ex_template.py b/3.13/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.13/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file diff --git a/3.14-minimal/Dockerfile.c10s b/3.14-minimal/Dockerfile.c10s new file mode 100644 index 00000000..5c1cf880 --- /dev/null +++ b/3.14-minimal/Dockerfile.c10s @@ -0,0 +1,104 @@ +FROM quay.io/centos/centos:stream10-minimal + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.14 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi10-python-314 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el10" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.14" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python314,python-314,rh-python314" \ + com.redhat.component="python-314-container" \ + name="sclorg/python-314-minimal-c10s" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.14-minimal/test/setup-test-app/ ubi10/python-314-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi10-python-314-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.14 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.14-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.14-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.14-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.14 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.14-minimal/Dockerfile.c9s b/3.14-minimal/Dockerfile.c9s new file mode 100644 index 00000000..9c9883b8 --- /dev/null +++ b/3.14-minimal/Dockerfile.c9s @@ -0,0 +1,104 @@ +FROM quay.io/centos/centos:stream9-minimal + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.14 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi9-python-314 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el9" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.14" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python314,python-314,rh-python314" \ + com.redhat.component="python-314-container" \ + name="sclorg/python-314-minimal-c9s" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.14-minimal/test/setup-test-app/ ubi9/python-314-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi9-python-314-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.14 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.14-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.14-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.14-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.14 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.14-minimal/Dockerfile.fedora b/3.14-minimal/Dockerfile.fedora new file mode 100644 index 00000000..ca0fde42 --- /dev/null +++ b/3.14-minimal/Dockerfile.fedora @@ -0,0 +1,104 @@ +FROM quay.io/fedora/fedora-minimal:43 + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.14 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi-python-314 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.14" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python314,python-314,rh-python314" \ + com.redhat.component="python-314-container" \ + name="fedora/python-314-minimal" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.14-minimal/test/setup-test-app/ ubi/python-314-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi-python-314-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.14 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.14-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.14-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.14-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.14 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.14-minimal/Dockerfile.rhel10 b/3.14-minimal/Dockerfile.rhel10 new file mode 100644 index 00000000..cad80210 --- /dev/null +++ b/3.14-minimal/Dockerfile.rhel10 @@ -0,0 +1,104 @@ +FROM ubi10-minimal:latest + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.14 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi10-python-314 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el10" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.14" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python314,python-314,rh-python314" \ + com.redhat.component="python-314-container" \ + name="ubi10/python-314-minimal" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.14-minimal/test/setup-test-app/ ubi10/python-314-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi10-python-314-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.14 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.14-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.14-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.14-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.14 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.14-minimal/Dockerfile.rhel9 b/3.14-minimal/Dockerfile.rhel9 new file mode 100644 index 00000000..0eea5238 --- /dev/null +++ b/3.14-minimal/Dockerfile.rhel9 @@ -0,0 +1,104 @@ +FROM ubi9/ubi-minimal:latest + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.14 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi9-python-314 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el9" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.14" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python314,python-314,rh-python314" \ + com.redhat.component="python-314-container" \ + name="ubi9/python-314-minimal" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.14-minimal/test/setup-test-app/ ubi9/python-314-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi9-python-314-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.14 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.14-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.14-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.14-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.14 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.14-minimal/README.md b/3.14-minimal/README.md new file mode 100644 index 00000000..8a24e8f4 --- /dev/null +++ b/3.14-minimal/README.md @@ -0,0 +1,125 @@ +Python 3.14 container image - minimal version +============================================ + +This container image is a special version of the [full Python 3.14 container image](https://github.com/sclorg/s2i-python-container/tree/master/3.14) +provided as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.14 applications. + +Because the minimal and full images work similarly, we document here only the differences and limitations +of the minimal container image. For the documentation of common features see the [full container image docs](https://github.com/sclorg/s2i-python-container/tree/master/3.14). + +The Python 3.14 minimal container image is currently considered a tech-preview and only available on quay.io. +The image is built on top of the [official CentOS Stream base containers](quay.io/centos/centos). + +To pull the Python 3.14 minimal container image to build on, run + +``` +podman pull quay.io/fedora/python-314-minimal +``` + +Description +----------- + +The full container image is a universal base image to build your containerized applications on top of. However, its universal nature +means that the resulting containers it produces consume a lot of disk space. This is caused mainly by the fact that the image contains +npm, compilers, header files and some other packages one might need to install and deploy their applications. + +Because size does matter for us and our customers, we have prepared this minimal container image with very limited subset +of installed packages. There are no compilers, no header files, no npm etc and the yum package manager is replaced with a minimalistic +reimplementation called microdnf, making the resulting container images much smaller. This creates some limitations +but we provide ways to workaround them. + +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Limitations +----------- + +1. There is only a very limited subset of packages installed. They are choosen carefully to satisfy most of the Python apps but your app might have some special needs. +1. There is no npm and nodejs. +1. There are no compilers and header files. Installation from Python wheels should still work but compilation from a source code is not supported out of the box. + +In the next chapter, we provide three possible workarounds for the mentioned limitations of the minimal container image. + +Possible solutions for the limitations +-------------------------------------- + +### Use the full container image + +It's easy at that. If you don't want to write your own Dockerfile and disk space is not a problem, use +the full universal container image and you should be fine. + +### Build your own container image on top of the minimal container image + +Let's say that your application depends on uwsgi. uwsgi cannot be installed from Python wheel and has to be +compiled from source which requires some additional packages to be installed - namely gcc for the compilation +itself and python3.14-devel containing Python header files. + +To solve that problem, you can use all the pieces provided by the minimal container image and just add one more +step to install the missing dependencies: + +``` +FROM python-314-minimal + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src + +# Install packages necessary for compiling uwsgi from source +RUN microdnf install -y gcc python3.14-devel +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you do it this way, your problem with the missing packages is solved. But there is also one disadvantage: the resulting +runtime image contains unnecessary compiler and Python header files. How to solve this? Uninstalling them at the end +of the Dockerfile is not really a solution but we have one. Keep reading. + +### Build on full image, run on minimal image + +Did you know that you can copy files from one image to another one during a build? That's the feature we are gonna use now. +We use the full container image with all compilers and other usefull packages installed to build our app and its dependencies +and we then move the result including the whole virtual environemnt to the minimal container image. + +This app needs mod_wsgi and to install (compile it from source) it, we'll need: httpd-devel for header files, gcc and redhat-rpm-config +as a compiler and configuratuion and finally python3.14-devel containing Python header files. There is no need to install those packages +manually because the full container image already contains them. However, the application needs httpd as a runtime dependency +so we need to install it to the minimal container image as well. + +``` +# Part 1 - build + +FROM python-314 as builder + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the application's dependencies from PyPI +RUN /usr/libexec/s2i/assemble + +# Part 2 - deploy + +FROM python-314-minimal + +# Copy app sources together with the whole virtual environment from the builder image +COPY --from=builder $APP_ROOT $APP_ROOT + +# Install httpd package - runtime dependency of our application +USER 0 +RUN microdnf install -y httpd +USER 1001 + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +This way, the resulting container image does contain only necessary dependencies and it's much lighter. diff --git a/3.14-minimal/root/opt/app-root/etc/generate_container_user b/3.14-minimal/root/opt/app-root/etc/generate_container_user new file mode 100644 index 00000000..a7fd74d6 --- /dev/null +++ b/3.14-minimal/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}:/bin/bash" >> $NSS_WRAPPER_PASSWD + + export NSS_WRAPPER_PASSWD + export NSS_WRAPPER_GROUP + + LD_PRELOAD=libnss_wrapper.so + export LD_PRELOAD +fi diff --git a/3.14-minimal/root/opt/wheels b/3.14-minimal/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.14-minimal/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.14-minimal/root/usr/bin/cgroup-limits b/3.14-minimal/root/usr/bin/cgroup-limits new file mode 100755 index 00000000..6fa19153 --- /dev/null +++ b/3.14-minimal/root/usr/bin/cgroup-limits @@ -0,0 +1,192 @@ +#!/usr/libexec/platform-python + +""" +Script for parsing cgroup information + +This script will read some limits from the cgroup system and parse +them, printing out "VARIABLE=VALUE" on each line for every limit that is +successfully read. Output of this script can be directly fed into +bash's export command. Recommended usage from a bash script: + + set -o errexit + export_vars=$(cgroup-limits) ; export $export_vars + +Variables currently supported: + MAX_MEMORY_LIMIT_IN_BYTES + Maximum possible limit MEMORY_LIMIT_IN_BYTES can have. This is + currently constant value of 9223372036854775807. + MEMORY_LIMIT_IN_BYTES + Maximum amount of user memory in bytes. If this value is set + to the same value as MAX_MEMORY_LIMIT_IN_BYTES, it means that + there is no limit set. The value is taken from + /sys/fs/cgroup/memory/memory.limit_in_bytes for cgroups v1 + and from /sys/fs/cgroup/memory.max for cgroups v2 + NUMBER_OF_CORES + Number of CPU cores that can be used. If both the cpu and cpuset + controllers specify a limit, the controller with the lowest CPU + limit takes precedence. For the cpu controller, the value is + calculated from /sys/fs/cgroup/cpu/cpu.cfs_{quota,period}_us for + cgroups v1 and from /sys/fs/cgroup/cpuset.cpus.effective for cgroups v2. + For the cpuset controller, the value is taken from + /sys/fs/cgroup/cpuset/cpuset.cpus for cgroups v1 and from + /sys/fs/cgroup/cpuset.cpus.effective for cgroups v2 + NO_MEMORY_LIMIT + Set to "true" if MEMORY_LIMIT_IN_BYTES is so high that the caller + can act as if no memory limit was set. Undefined otherwise. + +Note about non-root containers: + + Per podman-run(1) man page, on some systems, changing the resource limits + may not be allowed for non-root users. For more details, see + https://github.com/containers/podman/blob/main/troubleshooting.md#26-running-containers-with-resource-limits-fails-with-a-permissions-error. + +How to test this script: + + Run a container as root and see whether the output of available memory + and CPUs match what is either available on the host or specified via + cgroups limits by the container runtime (podman). + + For example: + + # This should return NO_MEMORY_LIMIT=true, NUMBER_OF_CORES= + sudo podman run -ti --rm quay.io/fedora/s2i-core /usr/bin/cgroup-limits + + # This should return MEMORY_LIMIT_IN_BYTES=2147483648, NUMBER_OF_CORES=3 + # 3 CPUs despite --cpus 4 was given is correct, because the cpuset 2-4 means + # running on 3 concrete processors only, which is lower limit than --cpus + # and thus is preferred + sudo podman run -ti --rm --cpus 4 --cpuset-cpus=2-4 --memory 2G quay.io/fedora/s2i-core /usr/bin/cgroup-limits +""" + +from __future__ import division +from __future__ import print_function +import sys +import subprocess + + +def _read_file(path): + try: + with open(path, 'r') as f: + return f.read().strip() + except IOError: + return None + + +def get_memory_limit(): + """ + Read memory limit, in bytes. + """ + + limit = _read_file('/sys/fs/cgroup/memory/memory.limit_in_bytes') + # If first file does not exist, try cgroups v2 file + limit = limit or _read_file('/sys/fs/cgroup/memory.max') + if limit is None or not limit.isdigit(): + if limit == 'max': + return 9223372036854775807 + print("Warning: Can't detect memory limit from cgroups", + file=sys.stderr) + return None + return int(limit) + + +def get_number_of_cores(): + """ + Read number of CPU cores. + """ + + limits = [l for l in [_read_cpu_quota(), _read_cpuset_size(), _read_nproc()] if l] + if not limits: + return None + return min(limits) + + +def _read_cpu_quota(): + + quota, period = None, None + + quota = _read_file("/sys/fs/cgroup/cpu/cpu.cfs_quota_us") + if quota: + # cgroups v1 + quota = quota.strip() + if quota == "-1": + return None + + period = _read_file("/sys/fs/cgroup/cpu/cpu.cfs_period_us") + if period: + period = period.strip() + + else: + # cgroups v2 + line = _read_file("/sys/fs/cgroup/cpu.max") + if line: + fields = line.split() + + if len(fields) >= 2: + quota = fields[0] + if quota == "max": + return None + period = fields[1] + + + if quota and quota.isdigit() and period and period.isdigit(): + return int(quota)//int(period) + + print("Warning: Can't detect cpu quota from cgroups", + file=sys.stderr) + return None + + +def _read_cpuset_size(): + + core_count = 0 + + line = _read_file('/sys/fs/cgroup/cpuset/cpuset.cpus') + # If first file does not exist, try cgroups v2 file + line = line or _read_file('/sys/fs/cgroup/cpuset.cpus.effective') + if line is None: + # None of the files above exists when running podman as non-root, + # so in that case, this warning is printed every-time + print("Warning: Can't detect cpuset size from cgroups, will use nproc", + file=sys.stderr) + return None + + for group in line.split(','): + core_ids = list(map(int, group.split('-'))) + if len(core_ids) == 2: + core_count += core_ids[1] - core_ids[0] + 1 + else: + core_count += 1 + + return core_count + + +def _read_nproc(): + """ + Returns number of cores without looking at cgroup limits. + This might work as the last resort when running a container as non-root + where cgroups v2 resource limits cannot be set without a specific + configuration (per podman-run(1) man page). + """ + try: + stdout, stderr = subprocess.Popen('nproc', stdout=subprocess.PIPE).communicate() + return int(stdout) + except EnvironmentError as e: + if e.errno != errno.ENOENT: + raise + return None + + +if __name__ == "__main__": + env_vars = { + "MAX_MEMORY_LIMIT_IN_BYTES": 9223372036854775807, + "MEMORY_LIMIT_IN_BYTES": get_memory_limit(), + "NUMBER_OF_CORES": get_number_of_cores() + } + + env_vars = {k: v for k, v in env_vars.items() if v is not None} + + if env_vars.get("MEMORY_LIMIT_IN_BYTES", 0) >= 92233720368547: + env_vars["NO_MEMORY_LIMIT"] = "true" + + for key, value in env_vars.items(): + print("{0}={1}".format(key, value)) diff --git a/3.14-minimal/root/usr/bin/fix-permissions b/3.14-minimal/root/usr/bin/fix-permissions new file mode 100755 index 00000000..ddd33ace --- /dev/null +++ b/3.14-minimal/root/usr/bin/fix-permissions @@ -0,0 +1,27 @@ +#!/bin/sh + +# Allow this script to fail without failing a build +set +e + +SYMLINK_OPT=${2:--L} + +# Fix permissions on the given directory or file to allow group read/write of +# regular files and execute of directories. + +[ $(id -u) -ne 0 ] && CHECK_OWNER=" -uid $(id -u)" + +# If argument does not exist, script will still exit with 0, +# but at least we'll see something went wrong in the log +if ! [ -e "$1" ] ; then + echo "ERROR: File or directory $1 does not exist." >&2 + # We still want to end successfully + exit 0 +fi + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} \! -gid 0 -exec chgrp 0 {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} \! -perm -g+rw -exec chmod g+rw {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} -perm /u+x -a \! -perm /g+x -exec chmod g+x {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} -type d \! -perm /g+x -exec chmod g+x {} + + +# Always end successfully +exit 0 diff --git a/3.14-minimal/root/usr/bin/rpm-file-permissions b/3.14-minimal/root/usr/bin/rpm-file-permissions new file mode 100755 index 00000000..8be1fb0d --- /dev/null +++ b/3.14-minimal/root/usr/bin/rpm-file-permissions @@ -0,0 +1,21 @@ +#!/bin/sh + +CHECK_DIRS="/ /opt /etc /usr /usr/bin /usr/lib /usr/lib64 /usr/share /usr/libexec" + +rpm_format="[%{FILESTATES:fstate} %7{FILEMODES:octal} %{FILENAMES:shescape}\n]" + +rpm -q --qf "$rpm_format" filesystem | while read line +do + eval "set -- $line" + + case $1 in + normal) ;; + *) continue ;; + esac + + case " $CHECK_DIRS " in + *" $3 "*) + chmod "${2: -4}" "$3" + ;; + esac +done diff --git a/3.14-minimal/s2i/bin/assemble b/3.14-minimal/s2i/bin/assemble new file mode 100755 index 00000000..b5cfeb6f --- /dev/null +++ b/3.14-minimal/s2i/bin/assemble @@ -0,0 +1,134 @@ +#!/bin/bash + +function is_django_installed() { + python -c "import django" &>/dev/null +} + +function should_collectstatic() { + is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] +} + +function virtualenv_bin() { + # New versions of Python (>3.6) should use venv module + # from stdlib instead of virtualenv package + python3.14 -m venv $1 +} + +# Install pipenv or micropipenv to the separate virtualenv to isolate it +# from system Python packages and packages in the main +# virtualenv. Executable is simlinked into ~/.local/bin +# to be accessible. This approach is inspired by pipsi +# (pip script installer). +function install_tool() { + echo "---> Installing $1 packaging tool ..." + VENV_DIR=$HOME/.local/venvs/$1 + virtualenv_bin "$VENV_DIR" + # First, try to install the tool without --isolated which means that if you + # have your own PyPI mirror, it will take it from there. If this try fails, try it + # again with --isolated which ignores external pip settings (env vars, config file) + # and installs the tool from PyPI (needs internet connetion). + # $1$2 combines package name with [extras] or version specifier if is defined as $2``` + if ! $VENV_DIR/bin/pip install -U $1$2; then + echo "WARNING: Installation of $1 failed, trying again from official PyPI with pip --isolated install" + $VENV_DIR/bin/pip install --isolated -U $1$2 # Combines package name with [extras] or version specifier if is defined as $2``` + fi + mkdir -p $HOME/.local/bin + ln -s $VENV_DIR/bin/$1 $HOME/.local/bin/$1 +} + +set -e + +# First of all, check that we don't have disallowed combination of ENVs +if [[ ! -z "$ENABLE_PIPENV" && ! -z "$ENABLE_MICROPIPENV" ]]; then + echo "ERROR: Pipenv and micropipenv cannot be enabled at the same time!" + # podman/buildah does not relay this exit code but it will be fixed hopefuly + # https://github.com/containers/buildah/issues/2305 + exit 3 +fi + +shopt -s dotglob +echo "---> Installing application source ..." +mv /tmp/src/* "$HOME" + +# set permissions for any installed artifacts +fix-permissions /opt/app-root -P + + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini +fi + +if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then + echo "---> Upgrading pip, setuptools and wheel to latest version ..." + if ! pip install -U pip setuptools wheel; then + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + pip install --isolated -U pip setuptools wheel + fi +fi + +if [[ ! -z "$ENABLE_PIPENV" ]]; then + if [[ ! -z "$PIN_PIPENV_VERSION" ]]; then + # Add == as a prefix to pipenv version, if defined + PIN_PIPENV_VERSION="==$PIN_PIPENV_VERSION" + fi + install_tool "pipenv" "$PIN_PIPENV_VERSION" + echo "---> Installing dependencies via pipenv ..." + if [[ -f Pipfile ]]; then + pipenv install --deploy + elif [[ -f requirements.txt ]]; then + pipenv install -r requirements.txt + fi + # pipenv check +elif [[ ! -z "$ENABLE_MICROPIPENV" ]]; then + install_tool "micropipenv" "[toml]" + echo "---> Installing dependencies via micropipenv ..." + # micropipenv detects Pipfile.lock and requirements.txt in this order + micropipenv install --deploy +elif [[ -f requirements.txt ]]; then + echo "---> Installing dependencies ..." + pip install -r requirements.txt +fi + +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." + pip install . +fi + +if should_collectstatic; then + ( + echo "---> Collecting Django static files ..." + + APP_HOME=$(readlink -f "${APP_HOME:-.}") + # Change the working directory to APP_HOME + PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" + cd "$APP_HOME" + + # Look for 'manage.py' in the current directory + manage_file=./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 -P diff --git a/3.14-minimal/s2i/bin/init-wrapper b/3.14-minimal/s2i/bin/init-wrapper new file mode 100755 index 00000000..26c8767d --- /dev/null +++ b/3.14-minimal/s2i/bin/init-wrapper @@ -0,0 +1,18 @@ +#!/bin/sh + +# Overview of how this script works: http://veithen.io/2014/11/16/sigterm-propagation.html +# Set a trap to kill the main app process when this +# init script receives SIGTERM or SIGINT +trap 'kill -s TERM $PID' TERM INT +# Execute the main application in the background +"$@" & +PID=$! +# wait command always terminates when trap is caught, even if the process hasn't finished yet +wait $PID +# Remove the trap and wait till the app process finishes completely +trap - TERM INT +# We wait again, since the first wait terminates when trap is caught +wait $PID +# Exit with the exit code of the app process +STATUS=$? +exit $STATUS diff --git a/3.14-minimal/s2i/bin/run b/3.14-minimal/s2i/bin/run new file mode 100755 index 00000000..bc0b3a15 --- /dev/null +++ b/3.14-minimal/s2i/bin/run @@ -0,0 +1,157 @@ +#!/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() { + # Using python and just python here because the script has + # platform-python in its shebang which we don't have in the minimal image + limit_vars=$(python /usr/bin/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)) + # According to http://docs.gunicorn.org/en/stable/design.html#how-many-workers, + # 12 workers should be enough to handle hundreds or thousands requests per second + default=$((default > 12 ? 12 : default)) + echo $default +} + +function maybe_run_in_init_wrapper() { + if [[ -z "$ENABLE_INIT_WRAPPER" ]]; then + exec "$@" + else + exec $STI_SCRIPTS_PATH/init-wrapper "$@" + fi +} + +# Look for gunicorn>=20.1.0 to utilize gunicorn.conf.py +if is_gunicorn_installed && [[ -f "gunicorn.conf.py" ]]; then + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + +APP_HOME=$(readlink -f "${APP_HOME:-.}") +# Change the working directory to APP_HOME +PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" +cd "$APP_HOME" + +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 + fi +fi + +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi +fi + +# Look for 'manage.py' in the current directory +manage_file=./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 not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + +if is_gunicorn_installed; then + setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) + # Look for wsgi module in the current directory + if [[ -z "$APP_MODULE" && -f "./wsgi.py" ]]; then + APP_MODULE=wsgi + elif [[ -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)} + + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" + fi +fi + +if is_django_installed; then + if [[ -f "$manage_file" ]]; then + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." + echo "WARNING: this is NOT a recommended way to run you application in production!" + echo "Consider using gunicorn or some other production web server." + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT + 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/3.14-minimal/s2i/bin/usage b/3.14-minimal/s2i/bin/usage new file mode 100755 index 00000000..8b449de5 --- /dev/null +++ b/3.14-minimal/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 < -- curl 127.0.0.1:8080 +EOF diff --git a/3.14-minimal/test/__init__.py b/3.14-minimal/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.14-minimal/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/3.8/test/app-home-test-app b/3.14-minimal/test/app-home-test-app similarity index 100% rename from 3.8/test/app-home-test-app rename to 3.14-minimal/test/app-home-test-app diff --git a/3.14-minimal/test/app-module-test-app b/3.14-minimal/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.14-minimal/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/3.14-minimal/test/check_imagestreams.py b/3.14-minimal/test/check_imagestreams.py new file mode 120000 index 00000000..56bb2be7 --- /dev/null +++ b/3.14-minimal/test/check_imagestreams.py @@ -0,0 +1 @@ +../../common/check_imagestreams.py \ No newline at end of file diff --git a/3.14-minimal/test/conftest.py b/3.14-minimal/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.14-minimal/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.14-minimal/test/django-different-port-test-app b/3.14-minimal/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.14-minimal/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/3.8/test/django-test-app b/3.14-minimal/test/django-test-app similarity index 100% rename from 3.8/test/django-test-app rename to 3.14-minimal/test/django-test-app diff --git a/3.14-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl b/3.14-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl new file mode 120000 index 00000000..f5c5b907 --- /dev/null +++ b/3.14-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/mod_wsgi.Dockerfile.tpl \ No newline at end of file diff --git a/3.14-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl b/3.14-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl new file mode 100644 index 00000000..874927da --- /dev/null +++ b/3.14-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl @@ -0,0 +1,20 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +ENV UPGRADE_PIP_TO_LATEST=1 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +# Install packages necessary for compiling uwsgi from source +# pkgconfig(python-3.14) is provided by both python3-devel in c9s +# and python3.14-devel in UBI8. +RUN microdnf install -y gcc "pkgconfig(python-3.14)" which +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run diff --git a/3.14-minimal/test/gunicorn-config-different-port-test-app b/3.14-minimal/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.14-minimal/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.14-minimal/test/gunicorn-different-port-test-app b/3.14-minimal/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.14-minimal/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.14-minimal/test/gunicorn-python-configfile-different-port-test-app b/3.14-minimal/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.14-minimal/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/3.14-minimal/test/imagestreams b/3.14-minimal/test/imagestreams new file mode 120000 index 00000000..7a0aee9c --- /dev/null +++ b/3.14-minimal/test/imagestreams @@ -0,0 +1 @@ +../../imagestreams \ No newline at end of file diff --git a/3.8/test/locale-test-app b/3.14-minimal/test/locale-test-app similarity index 100% rename from 3.8/test/locale-test-app rename to 3.14-minimal/test/locale-test-app diff --git a/3.14-minimal/test/micropipenv-requirements-test-app b/3.14-minimal/test/micropipenv-requirements-test-app new file mode 120000 index 00000000..cb2ddcf0 --- /dev/null +++ b/3.14-minimal/test/micropipenv-requirements-test-app @@ -0,0 +1 @@ +../../examples/micropipenv-requirements-test-app \ No newline at end of file diff --git a/3.8/test/pipenv-test-app/.gitignore b/3.14-minimal/test/micropipenv-test-app/.gitignore similarity index 100% rename from 3.8/test/pipenv-test-app/.gitignore rename to 3.14-minimal/test/micropipenv-test-app/.gitignore diff --git a/3.14-minimal/test/micropipenv-test-app/.s2i/environment b/3.14-minimal/test/micropipenv-test-app/.s2i/environment new file mode 100644 index 00000000..29efed4a --- /dev/null +++ b/3.14-minimal/test/micropipenv-test-app/.s2i/environment @@ -0,0 +1,5 @@ +ENABLE_MICROPIPENV=true +DISABLE_SETUP_PY_PROCESSING=true +# This tests second try to install micropipenv with --isolated +# because the first one won't work with following setting +PIP_INDEX_URL=https://example.com/ diff --git a/3.14-minimal/test/micropipenv-test-app/Pipfile b/3.14-minimal/test/micropipenv-test-app/Pipfile new file mode 100644 index 00000000..45aee705 --- /dev/null +++ b/3.14-minimal/test/micropipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.14" diff --git a/3.14-minimal/test/micropipenv-test-app/Pipfile.lock b/3.14-minimal/test/micropipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..11c134ec --- /dev/null +++ b/3.14-minimal/test/micropipenv-test-app/Pipfile.lock @@ -0,0 +1,242 @@ +{ + "_meta": { + "hash": { + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.14" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.0.2" + } + } +} diff --git a/3.8/test/pipenv-test-app/setup.py b/3.14-minimal/test/micropipenv-test-app/setup.py similarity index 100% rename from 3.8/test/pipenv-test-app/setup.py rename to 3.14-minimal/test/micropipenv-test-app/setup.py diff --git a/3.14-minimal/test/micropipenv-test-app/testapp.py b/3.14-minimal/test/micropipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.14-minimal/test/micropipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.8/test/numpy-test-app b/3.14-minimal/test/numpy-test-app similarity index 100% rename from 3.8/test/numpy-test-app rename to 3.14-minimal/test/numpy-test-app diff --git a/3.14-minimal/test/pipenv-and-micropipenv-should-fail-test-app b/3.14-minimal/test/pipenv-and-micropipenv-should-fail-test-app new file mode 120000 index 00000000..cf830645 --- /dev/null +++ b/3.14-minimal/test/pipenv-and-micropipenv-should-fail-test-app @@ -0,0 +1 @@ +../../src/test/pipenv-and-micropipenv-should-fail-test-app \ No newline at end of file diff --git a/3.14-minimal/test/pipenv-test-app/.gitignore b/3.14-minimal/test/pipenv-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/3.14-minimal/test/pipenv-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/3.8/test/pipenv-test-app/.s2i/environment b/3.14-minimal/test/pipenv-test-app/.s2i/environment similarity index 100% rename from 3.8/test/pipenv-test-app/.s2i/environment rename to 3.14-minimal/test/pipenv-test-app/.s2i/environment diff --git a/3.14-minimal/test/pipenv-test-app/Pipfile b/3.14-minimal/test/pipenv-test-app/Pipfile new file mode 100644 index 00000000..45aee705 --- /dev/null +++ b/3.14-minimal/test/pipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.14" diff --git a/3.14-minimal/test/pipenv-test-app/Pipfile.lock b/3.14-minimal/test/pipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..11c134ec --- /dev/null +++ b/3.14-minimal/test/pipenv-test-app/Pipfile.lock @@ -0,0 +1,242 @@ +{ + "_meta": { + "hash": { + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.14" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.0.2" + } + } +} diff --git a/3.14-minimal/test/pipenv-test-app/setup.py b/3.14-minimal/test/pipenv-test-app/setup.py new file mode 100644 index 00000000..e3134094 --- /dev/null +++ b/3.14-minimal/test/pipenv-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/3.14-minimal/test/pipenv-test-app/testapp.py b/3.14-minimal/test/pipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.14-minimal/test/pipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.14-minimal/test/poetry-src-layout-test-app b/3.14-minimal/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.14-minimal/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.14-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.14-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..530e67f1 --- /dev/null +++ b/3.14-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2026.0.3 diff --git a/3.14-minimal/test/pyuwsgi-pipenv-test-app/app.sh b/3.14-minimal/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.14-minimal/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.14-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.14-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.14-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.14-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py b/3.14-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.14-minimal/test/pyuwsgi-pipenv-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() diff --git a/3.14-minimal/test/run b/3.14-minimal/test/run new file mode 100755 index 00000000..8de24fe3 --- /dev/null +++ b/3.14-minimal/test/run @@ -0,0 +1,308 @@ +#!/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. +# +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,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${MINIMAL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) + +# TODO: Make command compatible for Mac users +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile_minimal +" + +if [[ -z $VERSION ]]; then + echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 + exit 1 +fi + +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} + +. test/test-lib.sh + +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 ..." + ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + return 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() { + 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 +} +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + info "Waiting for application container to start $CONTAINER_ARGS ..." + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && return 0 + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return 1 +} + +test_s2i_usage() { + info "Testing 's2i usage' ..." + ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage ..." + docker run --rm ${IMAGE_NAME} &>/dev/null +} + +test_scl_usage() { + local run_cmd="$1" + local expected="$2" + local cid_file="$3" + + info "Testing command availability in the image" + 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="$CID_FILE_DIR"/"$(mktemp -u -p . --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 + # Some test apps have tests in their startup code so we have to check + # that the container starts at all + ct_check_testcase_result $? + + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? + test_connection + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") +} + +test_from_dockerfile(){ + info "Test from Dockerfile" + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? +} + +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" + + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. + + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? + + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? + else + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." + fi + +} + +test_application_with_user() { + # test application with random user + CONTAINER_ARGS="--user 12345" test_application + +} + +test_application_enable_init_wrapper() { + # test application with init wrapper + CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue + + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" + + cleanup ${app} +done + +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.14-minimal/test/run-openshift-pytest b/3.14-minimal/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.14-minimal/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.14-minimal/test/run-pytest b/3.14-minimal/test/run-pytest new file mode 100755 index 00000000..9810fe89 --- /dev/null +++ b/3.14-minimal/test/run-pytest @@ -0,0 +1,16 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +if python3 -c 'import sys; sys.exit(0 if sys.version_info < (3,13) else 1)'; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.14-minimal/test/setup-cfg-test-app b/3.14-minimal/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.14-minimal/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/3.8/test/setup-requirements-test-app b/3.14-minimal/test/setup-requirements-test-app similarity index 100% rename from 3.8/test/setup-requirements-test-app rename to 3.14-minimal/test/setup-requirements-test-app diff --git a/3.8/test/setup-test-app b/3.14-minimal/test/setup-test-app similarity index 100% rename from 3.8/test/setup-test-app rename to 3.14-minimal/test/setup-test-app diff --git a/3.14-minimal/test/standalone-custom-pypi-index-test-app b/3.14-minimal/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.14-minimal/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/3.8/test/standalone-test-app b/3.14-minimal/test/standalone-test-app similarity index 100% rename from 3.8/test/standalone-test-app rename to 3.14-minimal/test/standalone-test-app diff --git a/3.8/test/test-lib.sh b/3.14-minimal/test/test-lib.sh similarity index 100% rename from 3.8/test/test-lib.sh rename to 3.14-minimal/test/test-lib.sh diff --git a/3.14-minimal/test/test_container_application.py b/3.14-minimal/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.14-minimal/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.14-minimal/test/test_container_basics.py b/3.14-minimal/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.14-minimal/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_deploy_templates.py b/3.14-minimal/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.14-minimal/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_helm_python_django_app.py b/3.14-minimal/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.14-minimal/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_helm_python_django_psql_persistent.py b/3.14-minimal/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.14-minimal/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_helm_python_imagestreams.py b/3.14-minimal/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.14-minimal/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_imagestreams_quickstart.py b/3.14-minimal/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.14-minimal/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_python_ex_standalone.py b/3.14-minimal/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.14-minimal/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_python_ex_template.py b/3.14-minimal/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.14-minimal/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file diff --git a/3.14-minimal/test/uwsgi-test-app b/3.14-minimal/test/uwsgi-test-app new file mode 120000 index 00000000..c3ccf241 --- /dev/null +++ b/3.14-minimal/test/uwsgi-test-app @@ -0,0 +1 @@ +../../examples/uwsgi-test-app \ No newline at end of file diff --git a/3.14/Dockerfile.c9s b/3.14/Dockerfile.c9s new file mode 100644 index 00000000..329aeaa7 --- /dev/null +++ b/3.14/Dockerfile.c9s @@ -0,0 +1,89 @@ +# This image provides a Python 3.14 environment you can use to run your Python +# applications. +FROM quay.io/sclorg/s2i-base-c9s:c9s + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.14 \ + 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 \ + ARCH=x86_64 + +ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.14" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python314,python-314,rh-python314" \ + com.redhat.component="$NAME" \ + name="sclorg/python-314-c9s" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.14/test/setup-test-app/ $FGC/$NAME python-sample-app" \ + maintainer="SoftwareCollections.org " + +RUN INSTALL_PKGS="python3.14 python3.14-devel python3.14-pip nss_wrapper-libs httpd \ + httpd-devel mod_ssl mod_auth_gssapi mod_ldap mod_session \ + atlas-devel gcc-gfortran libffi-devel libtool-ltdl enchant \ + krb5-devel" && \ + yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + yum -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.14/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.14/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.14/root/opt/wheels /opt/wheels +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 python3.14 -m venv ${APP_ROOT} && \ +# Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ +# We have to upgrade pip to a newer verison because \ +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ +# support platforms like ppc64le, aarch64 or armv7 \ +# We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ +# because it's tested better then whatever version from PyPI and contains useful patches. \ +# We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ +# to reinstall itself in the next build phases in the assemble script if user wants the latest version \ +${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +rm -r /opt/wheels && \ +chown -R 1001:0 ${APP_ROOT} && \ +fix-permissions ${APP_ROOT} -P && \ +# The following echo adds the unset command for the variables set below to the \ +# venv activation script. This prevents the virtual environment from being \ +# activated multiple times and also every time the prompt is rendered. \ +echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.8/Dockerfile.fedora b/3.14/Dockerfile.fedora similarity index 51% rename from 3.8/Dockerfile.fedora rename to 3.14/Dockerfile.fedora index 88327210..b46f3b3f 100644 --- a/3.8/Dockerfile.fedora +++ b/3.14/Dockerfile.fedora @@ -1,10 +1,10 @@ -# This image provides a Python 3.8 environment you can use to run your Python +# This image provides a Python 3.14 environment you can use to run your Python # applications. -FROM registry.fedoraproject.org/f32/s2i-base:latest +FROM quay.io/fedora/s2i-base:43 EXPOSE 8080 -ENV PYTHON_VERSION=3.8 \ +ENV PYTHON_VERSION=3.14 \ PATH=$HOME/.local/bin/:$PATH \ PYTHONUNBUFFERED=1 \ PYTHONIOENCODING=UTF-8 \ @@ -12,9 +12,7 @@ ENV PYTHON_VERSION=3.8 \ LANG=en_US.UTF-8 \ PIP_NO_CACHE_DIR=off - ENV NAME=python3 \ - VERSION=0 \ ARCH=x86_64 ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ @@ -29,40 +27,57 @@ on most platforms." LABEL summary="$SUMMARY" \ description="$DESCRIPTION" \ io.k8s.description="$DESCRIPTION" \ - io.k8s.display-name="Python 3.8" \ + io.k8s.display-name="Python 3.14" \ io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python38,python-38,rh-python38" \ + io.openshift.tags="builder,python,python314,python-314,rh-python314" \ com.redhat.component="$NAME" \ - name="$FGC/$NAME" \ - version="$VERSION" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.8/test/setup-test-app/ $FGC/$NAME python-sample-app" \ + name="fedora/$NAME-314" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.14/test/setup-test-app/ quay.io/fedora/$NAME-314 python-sample-app" \ maintainer="SoftwareCollections.org " -RUN INSTALL_PKGS="python3 python3-devel python3-setuptools python3-pip nss_wrapper \ +RUN INSTALL_PKGS="python3 python3-devel python3-setuptools python3-pip nss_wrapper-libs \ httpd httpd-devel atlas-devel gcc-gfortran libffi-devel \ - libtool-ltdl enchant redhat-rpm-config" && \ + libtool-ltdl enchant redhat-rpm-config krb5-devel" && \ dnf -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ dnf -y clean all --enablerepo='*' # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. -COPY ./s2i/bin/ $STI_SCRIPTS_PATH +COPY 3.14/s2i/bin/ $STI_SCRIPTS_PATH # Copy extra files to the image. -COPY ./root/ / +COPY 3.14/root/ / +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.14/root/opt/wheels /opt/wheels # - Create a Python virtual environment for use by any application to avoid # potential conflicts with Python packages preinstalled in the main Python # installation. # - 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 python3.8 -m venv ${APP_ROOT} && \ +RUN python3.14 -m venv ${APP_ROOT} && \ +# Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ +# We have to upgrade pip to a newer verison because \ +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ +# support platforms like ppc64le, aarch64 or armv7 \ +# We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ +# because it's tested better then whatever version from PyPI and contains useful patches. \ +# We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ +# to reinstall itself in the next build phases in the assemble script if user wants the latest version \ +${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +rm -r /opt/wheels && \ chown -R 1001:0 ${APP_ROOT} && \ -fix-permissions ${APP_ROOT} -P - -# For Fedora scl_enable isn't sourced automatically in s2i-core -# so virtualenv needs to be activated this way +fix-permissions ${APP_ROOT} -P && \ +# The following echo adds the unset command for the variables set below to the \ +# venv activation script. This prevents the virtual environment from being \ +# activated multiple times and also every time the prompt is rendered. \ +echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells ENV BASH_ENV="${APP_ROOT}/bin/activate" \ ENV="${APP_ROOT}/bin/activate" \ PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" diff --git a/3.14/Dockerfile.rhel9 b/3.14/Dockerfile.rhel9 new file mode 100644 index 00000000..44854fd9 --- /dev/null +++ b/3.14/Dockerfile.rhel9 @@ -0,0 +1,95 @@ +# This image provides a Python 3.14 environment you can use to run your Python +# applications. +FROM ubi9/s2i-base:1 + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.14 \ + PATH=$HOME/.local/bin/:$PATH \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi9-python-314 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off + +ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.14" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python314,python-314,rh-python314" \ + com.redhat.component="python-314-container" \ + name="ubi9/python-314" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.14/test/setup-test-app/ ubi9/python-314 python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi9-python-314" \ + maintainer="SoftwareCollections.org " + +RUN INSTALL_PKGS="python3.14 python3.14-devel python3.14-pip nss_wrapper-libs httpd \ + httpd-devel mod_ssl mod_auth_gssapi mod_ldap mod_session \ + atlas-devel gcc-gfortran libffi-devel libtool-ltdl enchant \ + krb5-devel" && \ + yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + # Remove redhat-logos-httpd (httpd dependency) to keep image size smaller. + rpm -e --nodeps redhat-logos-httpd && \ + yum -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.14/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.14/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.14/root/opt/wheels /opt/wheels +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.14 -m venv ${APP_ROOT} && \ + # Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ + # We have to upgrade pip to a newer verison because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions && \ + # The following echo adds the unset command for the variables set below to the \ + # venv activation script. This prevents the virtual environment from being \ + # activated multiple times and also every time the prompt is rendered. \ + echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.7/README.md b/3.14/README.md similarity index 59% rename from 3.7/README.md rename to 3.14/README.md index 0e4a7810..5193a5bc 100644 --- a/3.7/README.md +++ b/3.14/README.md @@ -1,11 +1,11 @@ -Python 3.7 container image -=================== - -This container image includes Python 3.7 as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.7 applications. -Users can choose between RHEL and CentOS based builder images. -The RHEL images are available in the [Red Hat Container Catalog](https://access.redhat.com/containers/), -the CentOS images are available on [Docker Hub](https://hub.docker.com/r/centos/), -and the Fedora images are available in [Fedora Registry](https://registry.fedoraproject.org/). +Python 3.14 container image +========================= + +This container image includes Python 3.14 as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.14 applications. +Users can choose between RHEL, CentOS Stream, and Fedora based builder images. +The RHEL images are available in the [Red Hat Container Catalog](https://catalog.redhat.com/software/containers/explore), +CentOS Stream images are available on [Quay.io](https://quay.io/organization/sclorg), +and the Fedora images are available on [Quay.io](https://quay.io/organization/fedora). The resulting image can be run using [podman](https://github.com/containers/libpod) or [docker](http://docker.io). @@ -14,8 +14,8 @@ Note: while the examples in this README are calling `podman`, you can replace an Description ----------- -Python 3.7 available as container is a base platform for -building and running various Python 3.7 applications and frameworks. +Python 3.14 available as container is a base platform for +building and running various Python 3.14 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, @@ -29,28 +29,132 @@ modules for their web applications. There is no guarantee for any specific npm o version, that is included in the image; those versions can be changed anytime and the nodejs itself is included just to make the npm work. -Usage ---------------------- +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Usage in Openshift +------------------ -For this, we will assume that you are using the supported image, available via `python:3.7` imagestream tag in Openshift. -Building a simple [python-sample-app](https://github.com/sclorg/s2i-python-container/tree/master/3.7/test/setup-test-app) application +For this, we will assume that you are using one of the supported images available via imagestream tags in Openshift, eg. `python:3.14-ubi9` +Building a simple [python-sample-app](https://github.com/sclorg/django-ex.git) application in Openshift can be achieved with the following step: ``` - oc new-app python:3.7~https://github.com/sclorg/s2i-python-container.git --context-dir=3.7/test/setup-test-app/ + oc new-app python:3.14-ubi9~https://github.com/sclorg/django-ex.git ``` -The same application can also be built using the standalone [S2I](https://github.com/openshift/source-to-image) application on systems that have it available: +**Accessing the application:** +``` +$ oc get pods +$ oc exec -- curl 127.0.0.1:8080 +``` - ``` - $ s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.7/test/setup-test-app/ python-sample-app - ``` +Source-to-Image framework and scripts +------------------------------------- +This image supports the [Source-to-Image](https://docs.openshift.com/container-platform/4.14/openshift_images/create-images.html#images-create-s2i_create-images) +(S2I) strategy in OpenShift. The Source-to-Image is an OpenShift framework +which makes it easy to write images that take application source code as +an input, use a builder image like this Python container image, and produce +a new image that runs the assembled application as an output. + +To support the Source-to-Image framework, important scripts are included in the builder image: + +* The `/usr/libexec/s2i/assemble` script inside the image is run to produce a new image with the application artifacts. +The script takes sources of a given application and places them into appropriate directories inside the image. +It utilizes some common patterns in Perl application development (see the **Environment variables** section below). +* The `/usr/libexec/s2i/run` script is set as the default command in the resulting container image (the new image with the application artifacts). +It runs your application according to settings in `APP_MODULE`, `APP_FILE` or `APP_SCRIPT` environment variables or it tries to detect the best +way automatically. + +Building an application using a Dockerfile +------------------------------------------ +Compared to the Source-to-Image strategy, using a Dockerfile is a more +flexible way to build a Python container image with an application. +Use a Dockerfile when Source-to-Image is not sufficiently flexible for you or +when you build the image outside of the OpenShift environment. + +To use the Python image in a Dockerfile, follow these steps: +#### 1. Pull a base builder image to build on -Where `` is the s2i-python image you [downloaded from RHEL, Centos or Fedora registry](../README.md#Download) or [built](../README.md#Build) from these sources. For example ubi8/python-36, centos/python-36-centos7 or f31/python3. +``` +podman pull quay.io/fedora/python-314 +``` -**Accessing the application:** +#### 2. Pull and application code + +An example application available at https://github.com/sclorg/django-ex.git is used here. Feel free to clone the repository for further experiments. +You can also take a look at code examples in s2i-python-container repository: https://github.com/sclorg/s2i-python-container/tree/master/examples + +``` +git clone https://github.com/sclorg/django-ex.git app-src ``` -$ curl 127.0.0.1:8080 + +#### 3. Prepare an application inside a container + +This step usually consists of at least these parts: + +* putting the application source into the container +* installing the dependencies +* setting the default command in the resulting image + +For all these three parts, users can either setup all manually and use commands `python` and `pip` explicitly in the Dockerfile, +or users can use the Source-to-Image scripts inside the image. + +The manual way comes with the highest level of flexibility but requires you to know how to work +with modules or software collections manually, how to setup virtual environment with the right version +of Python and many more. On the other hand, using Source-to-Image scripts makes your Dockerfile +prepared for a future flawless switch to a newer or different platform. + +To use the Source-to-Image scripts and build an image using a Dockerfile, create a Dockerfile with this content: + +``` +FROM quay.io/fedora/python-314 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you decide not to use the Source-to-Image scripts, you will need to manually tailor the Dockerfile to your application and its needs. +Example Dockerfile for a simple Django application: + +``` +FROM quay.io/fedora/python-314 + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 +``` + +#### 4. Build a new image from a Dockerfile prepared in the previous step + +``` +podman build -t python-app . +``` + +#### 5. Run the resulting image with final application + +``` +podman run -d python-app ``` Environment variables @@ -86,7 +190,7 @@ file inside your source code repository. 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/sclorg/s2i-python-container/tree/master/3.7/test/setup-test-app). + [setup-test-app](https://github.com/sclorg/s2i-python-container/tree/master/3.14/test/setup-test-app). * **APP_HOME** @@ -115,9 +219,9 @@ file inside your source code repository. 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_SETUP_PY_PROCESSING** +* **DISABLE_SETUP_PY_PROCESSING** / **DISABLE_PYPROJECT_TOML_PROCESSING** - Set this to a non-empty value to skip processing of setup.py script if you + Set this to a non-empty value to skip processing of setup.{py,cfg} or pyproject.toml script if you use `-e .` in requirements.txt to trigger its processing or you don't want your application to be installed into site-packages directory. @@ -127,6 +231,7 @@ file inside your source code repository. the higher-level Python packaging tool, to manage dependencies of the application. This should be used only if your project contains properly formated Pipfile and Pipfile.lock. + * **PIN_PIPENV_VERSION** Set this variable together with `ENABLE_PIPENV` to use a specific version of Pipenv. @@ -158,12 +263,18 @@ file inside your source code repository. the custom index, the container will try to install/update them from upstream PyPI afterwards. +* **PORT** + + HTTP(S) port your application should listen on. The default is 8080. + `PORT` is used only for Django development server and for Gunicorn + with the default configutation (no `APP_CONFIG` or `GUNICORN_CMD_ARGS` specified). + * **UPGRADE_PIP_TO_LATEST** Set this variable to a non-empty value to have the 'pip' program and related python packages (setuptools and wheel) 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. + before any Python packages are installed. If not set, the container will use + the stable pip version this container was built with, taken from a recent Fedora release. * **WEB_CONCURRENCY** @@ -196,7 +307,7 @@ However, if these files exist they will affect the behavior of the build process Configures various aspects of the project, including installation of dependencies, as documented - [here](https://packaging.python.org/en/latest/distributing.html#setup-py). + [here](https://packaging.python.org/guides/distributing-packages-using-setuptools/?highlight=distributing#setup-py). For most projects, it is sufficient to simply use `requirements.txt` or `Pipfile`. Set `DISABLE_SETUP_PY_PROCESSING` environment variable to true in order to skip processing of this file. @@ -221,6 +332,9 @@ following ways, in precedence order: If you have both Django and Gunicorn in your requirements, your Django project will automatically be served using Gunicorn. + The default setting for Gunicorn (`--bind=0.0.0.0:$PORT --access-logfile=-`) is applied + only if both `$APP_CONFIG` and `$GUNICORN_CMD_ARGS` are not defined. + * **Django development server** If you have Django in your requirements but don't have Gunicorn, then your @@ -244,7 +358,7 @@ following ways, in precedence order: application. Hot deploy ---------------------- +---------- If you are using Django, hot deploy will work out of the box. @@ -255,7 +369,7 @@ 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 podman's (or docker's) -[exec](https://github.com/containers/libpod/blob/master/docs/podman-exec.1.md) command: +[exec](https://github.com/containers/podman/blob/main/docs/source/markdown/podman-exec.1.md) command: ``` podman exec -it /bin/bash @@ -269,5 +383,6 @@ See also -------- Dockerfile and other sources are available on https://github.com/sclorg/s2i-python-container. In that repository you also can find another versions of Python environment Dockerfiles. -Dockerfile for CentOS is called `Dockerfile`, Dockerfile for RHEL7 is called `Dockerfile.rhel7`, -for RHEL8 it's `Dockerfile.rhel8` and the Fedora Dockerfile is called `Dockerfile.fedora`. +Dockerfile for RHEL8 is called `Dockerfile.rhel8`, for RHEL9 it's `Dockerfile.rhel9`, +for CentOS Stream 9 it's `Dockerfile.c9s`, for CentOS Stream 10 it's `Dockerfile.c10s`, +and the Fedora Dockerfile is called `Dockerfile.fedora`. diff --git a/3.14/root/opt/app-root/etc/generate_container_user b/3.14/root/opt/app-root/etc/generate_container_user new file mode 100644 index 00000000..a7fd74d6 --- /dev/null +++ b/3.14/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}:/bin/bash" >> $NSS_WRAPPER_PASSWD + + export NSS_WRAPPER_PASSWD + export NSS_WRAPPER_GROUP + + LD_PRELOAD=libnss_wrapper.so + export LD_PRELOAD +fi diff --git a/3.14/root/opt/wheels b/3.14/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.14/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.14/s2i/bin/assemble b/3.14/s2i/bin/assemble new file mode 100755 index 00000000..b5cfeb6f --- /dev/null +++ b/3.14/s2i/bin/assemble @@ -0,0 +1,134 @@ +#!/bin/bash + +function is_django_installed() { + python -c "import django" &>/dev/null +} + +function should_collectstatic() { + is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] +} + +function virtualenv_bin() { + # New versions of Python (>3.6) should use venv module + # from stdlib instead of virtualenv package + python3.14 -m venv $1 +} + +# Install pipenv or micropipenv to the separate virtualenv to isolate it +# from system Python packages and packages in the main +# virtualenv. Executable is simlinked into ~/.local/bin +# to be accessible. This approach is inspired by pipsi +# (pip script installer). +function install_tool() { + echo "---> Installing $1 packaging tool ..." + VENV_DIR=$HOME/.local/venvs/$1 + virtualenv_bin "$VENV_DIR" + # First, try to install the tool without --isolated which means that if you + # have your own PyPI mirror, it will take it from there. If this try fails, try it + # again with --isolated which ignores external pip settings (env vars, config file) + # and installs the tool from PyPI (needs internet connetion). + # $1$2 combines package name with [extras] or version specifier if is defined as $2``` + if ! $VENV_DIR/bin/pip install -U $1$2; then + echo "WARNING: Installation of $1 failed, trying again from official PyPI with pip --isolated install" + $VENV_DIR/bin/pip install --isolated -U $1$2 # Combines package name with [extras] or version specifier if is defined as $2``` + fi + mkdir -p $HOME/.local/bin + ln -s $VENV_DIR/bin/$1 $HOME/.local/bin/$1 +} + +set -e + +# First of all, check that we don't have disallowed combination of ENVs +if [[ ! -z "$ENABLE_PIPENV" && ! -z "$ENABLE_MICROPIPENV" ]]; then + echo "ERROR: Pipenv and micropipenv cannot be enabled at the same time!" + # podman/buildah does not relay this exit code but it will be fixed hopefuly + # https://github.com/containers/buildah/issues/2305 + exit 3 +fi + +shopt -s dotglob +echo "---> Installing application source ..." +mv /tmp/src/* "$HOME" + +# set permissions for any installed artifacts +fix-permissions /opt/app-root -P + + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini +fi + +if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then + echo "---> Upgrading pip, setuptools and wheel to latest version ..." + if ! pip install -U pip setuptools wheel; then + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + pip install --isolated -U pip setuptools wheel + fi +fi + +if [[ ! -z "$ENABLE_PIPENV" ]]; then + if [[ ! -z "$PIN_PIPENV_VERSION" ]]; then + # Add == as a prefix to pipenv version, if defined + PIN_PIPENV_VERSION="==$PIN_PIPENV_VERSION" + fi + install_tool "pipenv" "$PIN_PIPENV_VERSION" + echo "---> Installing dependencies via pipenv ..." + if [[ -f Pipfile ]]; then + pipenv install --deploy + elif [[ -f requirements.txt ]]; then + pipenv install -r requirements.txt + fi + # pipenv check +elif [[ ! -z "$ENABLE_MICROPIPENV" ]]; then + install_tool "micropipenv" "[toml]" + echo "---> Installing dependencies via micropipenv ..." + # micropipenv detects Pipfile.lock and requirements.txt in this order + micropipenv install --deploy +elif [[ -f requirements.txt ]]; then + echo "---> Installing dependencies ..." + pip install -r requirements.txt +fi + +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." + pip install . +fi + +if should_collectstatic; then + ( + echo "---> Collecting Django static files ..." + + APP_HOME=$(readlink -f "${APP_HOME:-.}") + # Change the working directory to APP_HOME + PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" + cd "$APP_HOME" + + # Look for 'manage.py' in the current directory + manage_file=./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 -P diff --git a/3.14/s2i/bin/init-wrapper b/3.14/s2i/bin/init-wrapper new file mode 100755 index 00000000..26c8767d --- /dev/null +++ b/3.14/s2i/bin/init-wrapper @@ -0,0 +1,18 @@ +#!/bin/sh + +# Overview of how this script works: http://veithen.io/2014/11/16/sigterm-propagation.html +# Set a trap to kill the main app process when this +# init script receives SIGTERM or SIGINT +trap 'kill -s TERM $PID' TERM INT +# Execute the main application in the background +"$@" & +PID=$! +# wait command always terminates when trap is caught, even if the process hasn't finished yet +wait $PID +# Remove the trap and wait till the app process finishes completely +trap - TERM INT +# We wait again, since the first wait terminates when trap is caught +wait $PID +# Exit with the exit code of the app process +STATUS=$? +exit $STATUS diff --git a/3.8/s2i/bin/run b/3.14/s2i/bin/run similarity index 54% rename from 3.8/s2i/bin/run rename to 3.14/s2i/bin/run index ee1e1d3b..7003f038 100755 --- a/3.8/s2i/bin/run +++ b/3.14/s2i/bin/run @@ -43,30 +43,54 @@ 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 + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + APP_HOME=$(readlink -f "${APP_HOME:-.}") # Change the working directory to APP_HOME PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" cd "$APP_HOME" -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" +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 fi - maybe_run_in_init_wrapper "$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) ..." - maybe_run_in_init_wrapper python "$APP_FILE" -else - test -n "$app_file_check" && (>&2 echo "ERROR: file '$app_file_check' not found.") && exit 1 +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi fi # Look for 'manage.py' in the current directory @@ -82,6 +106,11 @@ if should_migrate; then fi fi +# If not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + if is_gunicorn_installed; then setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) # Look for wsgi module in the current directory @@ -94,17 +123,27 @@ if is_gunicorn_installed; then 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" + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" fi fi if is_django_installed; then if [[ -f "$manage_file" ]]; then - echo "---> Serving application with 'manage.py runserver' ..." + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." echo "WARNING: this is NOT a recommended way to run you application in production!" echo "Consider using gunicorn or some other production web server." - maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:8080 + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT else echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." echo "Skipped 'python manage.py runserver'." diff --git a/3.14/s2i/bin/usage b/3.14/s2i/bin/usage new file mode 100755 index 00000000..9c6009c8 --- /dev/null +++ b/3.14/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 < -- curl 127.0.0.1:8080 +EOF diff --git a/3.14/test/__init__.py b/3.14/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.14/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/3.14/test/app-home-test-app b/3.14/test/app-home-test-app new file mode 120000 index 00000000..f6b42e77 --- /dev/null +++ b/3.14/test/app-home-test-app @@ -0,0 +1 @@ +../../examples/app-home-test-app \ No newline at end of file diff --git a/3.14/test/app-module-test-app b/3.14/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.14/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/3.14/test/check_imagestreams.py b/3.14/test/check_imagestreams.py new file mode 120000 index 00000000..56bb2be7 --- /dev/null +++ b/3.14/test/check_imagestreams.py @@ -0,0 +1 @@ +../../common/check_imagestreams.py \ No newline at end of file diff --git a/3.14/test/conftest.py b/3.14/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.14/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.14/test/django-different-port-test-app b/3.14/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.14/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/3.14/test/django-test-app b/3.14/test/django-test-app new file mode 120000 index 00000000..52cebf3b --- /dev/null +++ b/3.14/test/django-test-app @@ -0,0 +1 @@ +../../examples/django-test-app \ No newline at end of file diff --git a/3.14/test/from-dockerfile/Dockerfile.tpl b/3.14/test/from-dockerfile/Dockerfile.tpl new file mode 120000 index 00000000..ba88ccc4 --- /dev/null +++ b/3.14/test/from-dockerfile/Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/Dockerfile.tpl \ No newline at end of file diff --git a/3.14/test/from-dockerfile/Dockerfile_no_s2i.tpl b/3.14/test/from-dockerfile/Dockerfile_no_s2i.tpl new file mode 100644 index 00000000..da2f43ff --- /dev/null +++ b/3.14/test/from-dockerfile/Dockerfile_no_s2i.tpl @@ -0,0 +1,16 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 diff --git a/3.14/test/gunicorn-config-different-port-test-app b/3.14/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.14/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.14/test/gunicorn-different-port-test-app b/3.14/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.14/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.14/test/gunicorn-python-configfile-different-port-test-app b/3.14/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.14/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/3.14/test/imagestreams b/3.14/test/imagestreams new file mode 120000 index 00000000..7a0aee9c --- /dev/null +++ b/3.14/test/imagestreams @@ -0,0 +1 @@ +../../imagestreams \ No newline at end of file diff --git a/3.14/test/locale-test-app b/3.14/test/locale-test-app new file mode 120000 index 00000000..9105ec08 --- /dev/null +++ b/3.14/test/locale-test-app @@ -0,0 +1 @@ +../../examples/locale-test-app \ No newline at end of file diff --git a/3.14/test/micropipenv-requirements-test-app b/3.14/test/micropipenv-requirements-test-app new file mode 120000 index 00000000..cb2ddcf0 --- /dev/null +++ b/3.14/test/micropipenv-requirements-test-app @@ -0,0 +1 @@ +../../examples/micropipenv-requirements-test-app \ No newline at end of file diff --git a/3.14/test/micropipenv-test-app/.gitignore b/3.14/test/micropipenv-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/3.14/test/micropipenv-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/3.14/test/micropipenv-test-app/.s2i/environment b/3.14/test/micropipenv-test-app/.s2i/environment new file mode 100644 index 00000000..29efed4a --- /dev/null +++ b/3.14/test/micropipenv-test-app/.s2i/environment @@ -0,0 +1,5 @@ +ENABLE_MICROPIPENV=true +DISABLE_SETUP_PY_PROCESSING=true +# This tests second try to install micropipenv with --isolated +# because the first one won't work with following setting +PIP_INDEX_URL=https://example.com/ diff --git a/3.14/test/micropipenv-test-app/Pipfile b/3.14/test/micropipenv-test-app/Pipfile new file mode 100644 index 00000000..45aee705 --- /dev/null +++ b/3.14/test/micropipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.14" diff --git a/3.14/test/micropipenv-test-app/Pipfile.lock b/3.14/test/micropipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..11c134ec --- /dev/null +++ b/3.14/test/micropipenv-test-app/Pipfile.lock @@ -0,0 +1,242 @@ +{ + "_meta": { + "hash": { + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.14" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.0.2" + } + } +} diff --git a/3.14/test/micropipenv-test-app/setup.py b/3.14/test/micropipenv-test-app/setup.py new file mode 100644 index 00000000..e3134094 --- /dev/null +++ b/3.14/test/micropipenv-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/3.14/test/micropipenv-test-app/testapp.py b/3.14/test/micropipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.14/test/micropipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.5/test/mod-wsgi-test-app b/3.14/test/mod-wsgi-test-app similarity index 100% rename from 3.5/test/mod-wsgi-test-app rename to 3.14/test/mod-wsgi-test-app diff --git a/3.5/test/npm-virtualenv-uwsgi-test-app b/3.14/test/npm-virtualenv-uwsgi-test-app similarity index 100% rename from 3.5/test/npm-virtualenv-uwsgi-test-app rename to 3.14/test/npm-virtualenv-uwsgi-test-app diff --git a/3.14/test/numpy-test-app b/3.14/test/numpy-test-app new file mode 120000 index 00000000..b064f87f --- /dev/null +++ b/3.14/test/numpy-test-app @@ -0,0 +1 @@ +../../examples/numpy-test-app \ No newline at end of file diff --git a/3.14/test/pin-pipenv-version-test-app/.s2i/environment b/3.14/test/pin-pipenv-version-test-app/.s2i/environment new file mode 100644 index 00000000..530e67f1 --- /dev/null +++ b/3.14/test/pin-pipenv-version-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2026.0.3 diff --git a/3.14/test/pin-pipenv-version-test-app/app.sh b/3.14/test/pin-pipenv-version-test-app/app.sh new file mode 100755 index 00000000..4334c760 --- /dev/null +++ b/3.14/test/pin-pipenv-version-test-app/app.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.14/test/pin-pipenv-version-test-app/requirements.txt b/3.14/test/pin-pipenv-version-test-app/requirements.txt new file mode 100644 index 00000000..66d42051 --- /dev/null +++ b/3.14/test/pin-pipenv-version-test-app/requirements.txt @@ -0,0 +1,2 @@ +uWSGI +Flask diff --git a/3.14/test/pin-pipenv-version-test-app/wsgi.py b/3.14/test/pin-pipenv-version-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.14/test/pin-pipenv-version-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() diff --git a/3.14/test/pipenv-and-micropipenv-should-fail-test-app b/3.14/test/pipenv-and-micropipenv-should-fail-test-app new file mode 120000 index 00000000..cf830645 --- /dev/null +++ b/3.14/test/pipenv-and-micropipenv-should-fail-test-app @@ -0,0 +1 @@ +../../src/test/pipenv-and-micropipenv-should-fail-test-app \ No newline at end of file diff --git a/3.14/test/pipenv-test-app/.gitignore b/3.14/test/pipenv-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/3.14/test/pipenv-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/3.14/test/pipenv-test-app/.s2i/environment b/3.14/test/pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..4a4f7605 --- /dev/null +++ b/3.14/test/pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=true +DISABLE_SETUP_PY_PROCESSING=true diff --git a/3.14/test/pipenv-test-app/Pipfile b/3.14/test/pipenv-test-app/Pipfile new file mode 100644 index 00000000..45aee705 --- /dev/null +++ b/3.14/test/pipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.14" diff --git a/3.14/test/pipenv-test-app/Pipfile.lock b/3.14/test/pipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..11c134ec --- /dev/null +++ b/3.14/test/pipenv-test-app/Pipfile.lock @@ -0,0 +1,242 @@ +{ + "_meta": { + "hash": { + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.14" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.0.2" + } + } +} diff --git a/3.14/test/pipenv-test-app/setup.py b/3.14/test/pipenv-test-app/setup.py new file mode 100644 index 00000000..e3134094 --- /dev/null +++ b/3.14/test/pipenv-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/3.14/test/pipenv-test-app/testapp.py b/3.14/test/pipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.14/test/pipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.14/test/poetry-src-layout-test-app b/3.14/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.14/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.14/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.14/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..530e67f1 --- /dev/null +++ b/3.14/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2026.0.3 diff --git a/3.14/test/pyuwsgi-pipenv-test-app/app.sh b/3.14/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.14/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.14/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.14/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.14/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.14/test/pyuwsgi-pipenv-test-app/wsgi.py b/3.14/test/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.14/test/pyuwsgi-pipenv-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() diff --git a/3.14/test/run b/3.14/test/run new file mode 100755 index 00000000..134639eb --- /dev/null +++ b/3.14/test/run @@ -0,0 +1,312 @@ +#!/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. +# +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,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${FULL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) + +# TODO: Make command compatible for Mac users +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile +" + +if [[ -z $VERSION ]]; then + echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 + exit 1 +fi + +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} + +. test/test-lib.sh + +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 ..." + ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + return 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() { + 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 +} +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + info "Waiting for application container to start $CONTAINER_ARGS ..." + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && return 0 + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return 1 +} + +test_s2i_usage() { + info "Testing 's2i usage' ..." + ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage ..." + docker run --rm ${IMAGE_NAME} &>/dev/null +} + +test_scl_usage() { + local run_cmd="$1" + local expected="$2" + local cid_file="$3" + + info "Testing command availability in the image" + 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="$CID_FILE_DIR"/"$(mktemp -u -p . --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 + # Some test apps have tests in their startup code so we have to check + # that the container starts at all + ct_check_testcase_result $? + + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? + test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" + ct_check_testcase_result $? + test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" + ct_check_testcase_result $? + test_connection + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") +} + +test_from_dockerfile(){ + info "Test from Dockerfile" + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? +} + +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" + + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. + + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? + + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? + else + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." + fi + +} + +test_application_with_user() { + # test application with random user + CONTAINER_ARGS="--user 12345" test_application + +} + +test_application_enable_init_wrapper() { + # test application with init wrapper + CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue + + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" + + cleanup ${app} +done + +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.14/test/run-openshift-pytest b/3.14/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.14/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.14/test/run-pytest b/3.14/test/run-pytest new file mode 100755 index 00000000..9810fe89 --- /dev/null +++ b/3.14/test/run-pytest @@ -0,0 +1,16 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +if python3 -c 'import sys; sys.exit(0 if sys.version_info < (3,13) else 1)'; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.14/test/setup-cfg-test-app b/3.14/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.14/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/3.14/test/setup-requirements-test-app b/3.14/test/setup-requirements-test-app new file mode 120000 index 00000000..c909ccb9 --- /dev/null +++ b/3.14/test/setup-requirements-test-app @@ -0,0 +1 @@ +../../examples/setup-requirements-test-app \ No newline at end of file diff --git a/3.14/test/setup-test-app b/3.14/test/setup-test-app new file mode 120000 index 00000000..1b49f26f --- /dev/null +++ b/3.14/test/setup-test-app @@ -0,0 +1 @@ +../../examples/setup-test-app \ No newline at end of file diff --git a/3.14/test/standalone-custom-pypi-index-test-app b/3.14/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.14/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/3.14/test/standalone-test-app b/3.14/test/standalone-test-app new file mode 120000 index 00000000..3f158948 --- /dev/null +++ b/3.14/test/standalone-test-app @@ -0,0 +1 @@ +../../examples/standalone-test-app \ No newline at end of file diff --git a/3.14/test/test-lib.sh b/3.14/test/test-lib.sh new file mode 120000 index 00000000..1ac99b93 --- /dev/null +++ b/3.14/test/test-lib.sh @@ -0,0 +1 @@ +../../common/test-lib.sh \ No newline at end of file diff --git a/3.14/test/test_container_application.py b/3.14/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.14/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.14/test/test_container_basics.py b/3.14/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.14/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.14/test/test_ocp_deploy_templates.py b/3.14/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.14/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.14/test/test_ocp_helm_python_django_app.py b/3.14/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.14/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.14/test/test_ocp_helm_python_django_psql_persistent.py b/3.14/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.14/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.14/test/test_ocp_helm_python_imagestreams.py b/3.14/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.14/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.14/test/test_ocp_imagestreams_quickstart.py b/3.14/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.14/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.14/test/test_ocp_python_ex_standalone.py b/3.14/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.14/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.14/test/test_ocp_python_ex_template.py b/3.14/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.14/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file diff --git a/3.3/Dockerfile b/3.3/Dockerfile deleted file mode 100644 index 059ba056..00000000 --- a/3.3/Dockerfile +++ /dev/null @@ -1,56 +0,0 @@ -# This image provides a Python 3.3 environment you can use to run your Python -# applications. -FROM openshift/base-centos7 - -MAINTAINER SoftwareCollections.org - -EXPOSE 8080 - -ENV PYTHON_VERSION=3.3 \ - 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 - -LABEL io.k8s.description="Platform for building and running Python 3.3 applications" \ - io.k8s.display-name="Python 3.3" \ - io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python33" - -RUN yum install -y centos-release-scl && \ - INSTALL_PKGS="python33 python33-python-devel python33-python-setuptools nss_wrapper httpd24 \ - httpd24-httpd-devel httpd24-mod_ssl httpd24-mod_auth_kerb httpd24-mod_ldap httpd24-mod_session \ - atlas-devel gcc-gfortran libffi-devel libtool-ltdl" && \ - yum install -y --setopt=tsflags=nodocs --enablerepo=centosplus $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - # Remove centos-logos (httpd dependency, ~20MB of graphics) to keep image - # size smaller. - rpm -e --nodeps centos-logos && \ - yum clean all -y - -# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. -COPY ./s2i/bin/ $STI_SCRIPTS_PATH - -# Each language image can have 'contrib' a directory with extra files needed to -# run and build the applications. -COPY ./contrib/ /opt/app-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 source scl_source enable python33 && \ - virtualenv /opt/app-root && \ - source /opt/app-root/bin/activate && \ - pip install -U pip==1.5.6 - -# 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 ug+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/3.3/Dockerfile.rhel7 b/3.3/Dockerfile.rhel7 deleted file mode 100644 index 4b70126c..00000000 --- a/3.3/Dockerfile.rhel7 +++ /dev/null @@ -1,63 +0,0 @@ -# This image provides a Python 3.3 environment you can use to run your Python -# applications. -FROM openshift/base-rhel7 - -EXPOSE 8080 - -ENV PYTHON_VERSION=3.3 \ - 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 - -# Labels consumed by Red Hat build service. -LABEL com.redhat.component="openshift-sti-python-docker" \ - name="openshift3/python-33-rhel7" \ - version="3.3" \ - release="1" \ - architecture="x86_64" \ - io.k8s.description="Platform for building and running Python 3.3 applications" \ - io.k8s.display-name="Python 3.3" \ - io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python33" - -# To use subscription inside container yum command has to be run first (before yum-config-manager) -# https://access.redhat.com/solutions/1443553 -RUN yum repolist > /dev/null && \ - yum-config-manager --enable rhel-server-rhscl-7-rpms && \ - yum-config-manager --enable rhel-7-server-optional-rpms && \ - yum-config-manager --enable rhel-7-server-ose-3.2-rpms && \ - yum-config-manager --disable epel >/dev/null || : && \ - INSTALL_PKGS="python33 python33-python-devel python33-python-setuptools python33-python-pip nss_wrapper httpd24 \ - httpd24-httpd-devel httpd24-mod_ssl httpd24-mod_auth_kerb httpd24-mod_ldap httpd24-mod_session \ - atlas-devel gcc-gfortran libffi-devel libtool-ltdl" && \ - yum install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - # Remove redhat-logos (httpd dependency) to keep image size smaller. - rpm -e --nodeps redhat-logos && \ - yum clean all -y - -# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. -COPY ./s2i/bin/ $STI_SCRIPTS_PATH - -# Each language image can have 'contrib' a directory with extra files needed to -# run and build the applications. -COPY ./contrib/ /opt/app-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 source scl_source enable python33 && \ - virtualenv /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 ug+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/3.3/README.md b/3.3/README.md deleted file mode 100644 index ae4c88c9..00000000 --- a/3.3/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Python container image -=================== - -**The Python 3.3 image is deprecated.** diff --git a/3.3/cccp.yml b/3.3/cccp.yml deleted file mode 100644 index f1133510..00000000 --- a/3.3/cccp.yml +++ /dev/null @@ -1,3 +0,0 @@ -# This is for the purpose of building this container -# on the centos container pipeline. -job-id: python-33-centos7 diff --git a/3.3/contrib/etc/scl_enable b/3.3/contrib/etc/scl_enable deleted file mode 100644 index ff2c622f..00000000 --- a/3.3/contrib/etc/scl_enable +++ /dev/null @@ -1,6 +0,0 @@ -# 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 python33 -source /opt/app-root/bin/activate diff --git a/3.3/s2i/bin/assemble b/3.3/s2i/bin/assemble deleted file mode 100755 index 0d69d0ed..00000000 --- a/3.3/s2i/bin/assemble +++ /dev/null @@ -1,66 +0,0 @@ -#!/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 - if [ -n "$PIP_INDEX_URL" ]; then - echo "---> Installing dependencies via $PIP_INDEX_URL ..." - else - echo "---> Installing dependencies ..." - fi - 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 - -# remove pip temporary directory -rm -rf /tmp/pip_build_default diff --git a/3.3/s2i/bin/run b/3.3/s2i/bin/run deleted file mode 100755 index b0c1ed35..00000000 --- a/3.3/s2i/bin/run +++ /dev/null @@ -1,103 +0,0 @@ -#!/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 40 MiB and additional 40 MiB for every worker - local default=$(((${MEMORY_LIMIT_IN_BYTES:-MAX_MEMORY_LIMIT_IN_BYTES}/1024/1024 - 40) / 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/3.3/s2i/bin/usage b/3.3/s2i/bin/usage deleted file mode 100755 index b8e9ef6f..00000000 --- a/3.3/s2i/bin/usage +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -DISTRO=`cat /etc/*-release | grep ^ID= | grep -Po '".*?"' | tr -d '"'` - -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.3." "${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/3.3/test/standalone-test-app/app.py b/3.3/test/standalone-test-app/app.py deleted file mode 100644 index 5ad0d390..00000000 --- a/3.3/test/standalone-test-app/app.py +++ /dev/null @@ -1,30 +0,0 @@ -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/3.3/test/standalone-test-app/requirements.txt b/3.3/test/standalone-test-app/requirements.txt deleted file mode 100644 index 8f22dccf..00000000 --- a/3.3/test/standalone-test-app/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -gunicorn diff --git a/3.3/test/uwsgi-test-app/app.sh b/3.3/test/uwsgi-test-app/app.sh deleted file mode 100755 index f6c873e4..00000000 --- a/3.3/test/uwsgi-test-app/app.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -exec uwsgi \ - --http-socket :8080 \ - --die-on-term \ - --master \ - --single-interpreter \ - --enable-threads \ - --threads=5 \ - --thunder-lock \ - --module wsgi diff --git a/3.4/Dockerfile b/3.4/Dockerfile deleted file mode 100644 index fd66d6a9..00000000 --- a/3.4/Dockerfile +++ /dev/null @@ -1,67 +0,0 @@ -# This image provides a Python 3.4 environment you can use to run your Python -# applications. -FROM centos/s2i-base-centos7 - -EXPOSE 8080 - -ENV PYTHON_VERSION=3.4 \ - 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 SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ - DESCRIPTION="Python $PYTHON_VERSION available as 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.4" \ - io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python34,rh-python34" \ - com.redhat.component="python34-docker" \ - name="centos/python-34-centos7" \ - version="3.4" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.4/test/setup-test-app/ centos/python-34-centos7 python-sample-app" \ - maintainer="SoftwareCollections.org " - -RUN INSTALL_PKGS="rh-python34 rh-python34-python-devel rh-python34-python-setuptools rh-python34-python-pip nss_wrapper \ - httpd24 httpd24-httpd-devel httpd24-mod_ssl httpd24-mod_auth_kerb httpd24-mod_ldap \ - httpd24-mod_session atlas-devel gcc-gfortran libffi-devel libtool-ltdl enchant" && \ - yum install -y centos-release-scl && \ - yum -y --setopt=tsflags=nodocs install --enablerepo=centosplus $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - # Remove centos-logos (httpd dependency) to keep image size smaller. - rpm -e --nodeps centos-logos && \ - yum -y clean all --enablerepo='*' - -# 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. -# - 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 source scl_source enable rh-python34 && \ - virtualenv ${APP_ROOT} && \ - chown -R 1001:0 ${APP_ROOT} && \ - fix-permissions ${APP_ROOT} -P && \ - rpm-file-permissions - -USER 1001 - -# Set the default CMD to print the usage of the language image. -CMD $STI_SCRIPTS_PATH/usage diff --git a/3.4/Dockerfile.rhel7 b/3.4/Dockerfile.rhel7 deleted file mode 100644 index 41896671..00000000 --- a/3.4/Dockerfile.rhel7 +++ /dev/null @@ -1,68 +0,0 @@ -# This image provides a Python 3.4 environment you can use to run your Python -# applications. -FROM rhscl/s2i-base-rhel7 - -EXPOSE 8080 - -ENV PYTHON_VERSION=3.4 \ - 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 SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ - DESCRIPTION="Python $PYTHON_VERSION available as 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.4" \ - io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python34,rh-python34" \ - com.redhat.component="rh-python34-docker" \ - name="rhscl/python-34-rhel7" \ - version="3.4" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.4/test/setup-test-app/ rhscl/python-34-rhel7 python-sample-app" \ - maintainer="SoftwareCollections.org " - -RUN INSTALL_PKGS="rh-python34 rh-python34-python-devel rh-python34-python-setuptools rh-python34-python-pip nss_wrapper \ - httpd24 httpd24-httpd-devel httpd24-mod_ssl httpd24-mod_auth_kerb httpd24-mod_ldap \ - httpd24-mod_session atlas-devel gcc-gfortran libffi-devel libtool-ltdl enchant" && \ - yum install -y yum-utils && \ - prepare-yum-repositories rhel-server-rhscl-7-rpms && \ - yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - # Remove redhat-logos (httpd dependency) to keep image size smaller. - rpm -e --nodeps redhat-logos && \ - yum -y clean all --enablerepo='*' - -# 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. -# - 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 source scl_source enable rh-python34 && \ - virtualenv ${APP_ROOT} && \ - chown -R 1001:0 ${APP_ROOT} && \ - fix-permissions ${APP_ROOT} -P && \ - rpm-file-permissions - -USER 1001 - -# Set the default CMD to print the usage of the language image. -CMD $STI_SCRIPTS_PATH/usage diff --git a/3.4/README.md b/3.4/README.md deleted file mode 100644 index d47127ab..00000000 --- a/3.4/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Python 3.4 container image -=================== - -**The Python 3.4 image is deprecated.** diff --git a/3.4/cccp.yml b/3.4/cccp.yml deleted file mode 100644 index b084fadf..00000000 --- a/3.4/cccp.yml +++ /dev/null @@ -1,3 +0,0 @@ -# This is for the purpose of building this container -# on the centos container pipeline. -job-id: python-34-centos7 diff --git a/3.4/content_sets.yml b/3.4/content_sets.yml deleted file mode 100644 index 6c39e014..00000000 --- a/3.4/content_sets.yml +++ /dev/null @@ -1,10 +0,0 @@ -# This is a file defining which content sets are needed to update content in -# this image. Data provided here helps determine which images are vulnerable to -# specific CVEs. Generally you should only need to update this file when: -# 1. You start depending on new product -# 2. You are preparing new product release and your content sets will change ---- -x86_64: -- rhel-7-server-rpms -- rhel-7-server-optional-rpms -- rhel-server-rhscl-7-rpms diff --git a/3.4/root/opt/app-root/etc/scl_enable b/3.4/root/opt/app-root/etc/scl_enable deleted file mode 100644 index 0186a31d..00000000 --- a/3.4/root/opt/app-root/etc/scl_enable +++ /dev/null @@ -1,6 +0,0 @@ -# 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 $NODEJS_SCL rh-python34 -source /opt/app-root/bin/activate diff --git a/3.4/s2i/bin/assemble b/3.4/s2i/bin/assemble deleted file mode 100755 index 7af00ad5..00000000 --- a/3.4/s2i/bin/assemble +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash - -function is_django_installed() { - python -c "import django" &>/dev/null -} - -function should_collectstatic() { - is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] -} - -function virtualenv_bin() { - if head "/etc/redhat-release" | grep -q "^Fedora"; then - virtualenv-${PYTHON_VERSION} $1 - else - virtualenv $1 - fi -} - -# Install pipenv to the separate virtualenv to isolate it -# from system Python packages and packages in the main -# virtualenv. Executable is simlinked into ~/.local/bin -# to be accessible. This approach is inspired by pipsi -# (pip script installer). -function install_pipenv() { - echo "---> Installing pipenv packaging tool ..." - pip install -U virtualenv - VENV_DIR=$HOME/.local/venvs/pipenv - virtualenv_bin "$VENV_DIR" - $VENV_DIR/bin/pip --isolated install -U pipenv - mkdir -p $HOME/.local/bin - ln -s $VENV_DIR/bin/pipenv $HOME/.local/bin/pipenv -} - -set -e - -shopt -s dotglob -echo "---> Installing application source ..." -mv /tmp/src/* ./ - -if [[ ! -z "$UPGRADE_PIP_TO_LATEST" || ! -z "$ENABLE_PIPENV" ]]; then - echo "---> Upgrading pip to latest version ..." - pip install -U pip setuptools wheel -fi - -if [[ ! -z "$ENABLE_PIPENV" ]]; then - install_pipenv - echo "---> Installing dependencies via pipenv ..." - if [[ -f Pipfile ]]; then - pipenv install --deploy - elif [[ -f requirements.txt ]]; then - pipenv install -r requirements.txt - fi - # pipenv check -elif [[ -f requirements.txt ]]; then - echo "---> Installing dependencies ..." - pip install -r requirements.txt -fi - -if [[ -f setup.py && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then - echo "---> Installing application ..." - pip install . -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/3.4/s2i/bin/run b/3.4/s2i/bin/run deleted file mode 100755 index c4865b56..00000000 --- a/3.4/s2i/bin/run +++ /dev/null @@ -1,106 +0,0 @@ -#!/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)) - # According to http://docs.gunicorn.org/en/stable/design.html#how-many-workers, - # 12 workers should be enough to handle hundreds or thousands requests per second - default=$((default > 12 ? 12 : 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/3.4/s2i/bin/usage b/3.4/s2i/bin/usage deleted file mode 100755 index 26eb67fd..00000000 --- a/3.4/s2i/bin/usage +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -DISTRO=`cat /etc/*-release | grep ^ID= | grep -Po '".*?"' | tr -d '"'` -NAMESPACE=centos -[[ $DISTRO =~ rhel* ]] && NAMESPACE=rhscl - -cat </dev/null -} - -function should_collectstatic() { - is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] -} - -function virtualenv_bin() { - if head "/etc/redhat-release" | grep -q "^CentOS Linux release 7" || \ - head "/etc/redhat-release" | grep -q "^Red Hat Enterprise Linux\( Server\)\? release 7" || \ - head "/etc/redhat-release" | grep -q "^Fedora release 29 \(Twenty Nine\)\?"; then - virtualenv $1 - else - virtualenv-${PYTHON_VERSION} $1 - fi -} - -# Install pipenv to the separate virtualenv to isolate it -# from system Python packages and packages in the main -# virtualenv. Executable is simlinked into ~/.local/bin -# to be accessible. This approach is inspired by pipsi -# (pip script installer). -function install_pipenv() { - echo "---> Installing pipenv packaging tool ..." - pip install -U virtualenv - VENV_DIR=$HOME/.local/venvs/pipenv - virtualenv_bin "$VENV_DIR" - $VENV_DIR/bin/pip --isolated install -U pipenv - mkdir -p $HOME/.local/bin - ln -s $VENV_DIR/bin/pipenv $HOME/.local/bin/pipenv -} - -set -e - -shopt -s dotglob -echo "---> Installing application source ..." -mv /tmp/src/* "$HOME" - -if [[ ! -z "$UPGRADE_PIP_TO_LATEST" || ! -z "$ENABLE_PIPENV" ]]; then - echo "---> Upgrading pip to latest version ..." - pip install -U pip setuptools wheel -fi - -if [[ ! -z "$ENABLE_PIPENV" ]]; then - install_pipenv - echo "---> Installing dependencies via pipenv ..." - if [[ -f Pipfile ]]; then - pipenv install --deploy - elif [[ -f requirements.txt ]]; then - pipenv install -r requirements.txt - fi - # pipenv check -elif [[ -f requirements.txt ]]; then - echo "---> Installing dependencies ..." - pip install -r requirements.txt -fi - -if [[ -f setup.py && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then - echo "---> Installing application ..." - pip install . -fi - -if should_collectstatic; then - ( - echo "---> Collecting Django static files ..." - - APP_HOME=$(readlink -f "${APP_HOME:-.}") - # Change the working directory to APP_HOME - PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" - cd "$APP_HOME" - - # Look for 'manage.py' in the current directory - manage_file=./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/3.5/s2i/bin/usage b/3.5/s2i/bin/usage deleted file mode 100755 index cfe10e51..00000000 --- a/3.5/s2i/bin/usage +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -DISTRO=`cat /etc/*-release | grep ^ID= | grep -Po '".*?"' | tr -d '"'` -NAMESPACE=centos -[[ $DISTRO =~ rhel* ]] && NAMESPACE=rhscl - -cat <> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" USER 1001 diff --git a/3.6/README.md b/3.6/README.md index aafc7019..cb418efb 100644 --- a/3.6/README.md +++ b/3.6/README.md @@ -1,11 +1,11 @@ Python 3.6 container image -=================== +========================= This container image includes Python 3.6 as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.6 applications. -Users can choose between RHEL and CentOS based builder images. -The RHEL images are available in the [Red Hat Container Catalog](https://access.redhat.com/containers/), -the CentOS images are available on [Docker Hub](https://hub.docker.com/r/centos/), -and the Fedora images are available in [Fedora Registry](https://registry.fedoraproject.org/). +Users can choose between RHEL, CentOS Stream, and Fedora based builder images. +The RHEL images are available in the [Red Hat Container Catalog](https://catalog.redhat.com/software/containers/explore), +CentOS Stream images are available on [Quay.io](https://quay.io/organization/sclorg), +and the Fedora images are available on [Quay.io](https://quay.io/organization/fedora). The resulting image can be run using [podman](https://github.com/containers/libpod) or [docker](http://docker.io). @@ -29,28 +29,132 @@ modules for their web applications. There is no guarantee for any specific npm o version, that is included in the image; those versions can be changed anytime and the nodejs itself is included just to make the npm work. -Usage ---------------------- +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Usage in Openshift +------------------ -For this, we will assume that you are using the supported image, available via `python:3.6` imagestream tag in Openshift. -Building a simple [python-sample-app](https://github.com/sclorg/s2i-python-container/tree/master/3.6/test/setup-test-app) application +For this, we will assume that you are using one of the supported images available via imagestream tags in Openshift, eg. `python:3.6-ubi9` +Building a simple [python-sample-app](https://github.com/sclorg/django-ex.git) application in Openshift can be achieved with the following step: ``` - oc new-app python:3.6~https://github.com/sclorg/s2i-python-container.git --context-dir=3.6/test/setup-test-app/ + oc new-app python:3.6-ubi9~https://github.com/sclorg/django-ex.git ``` -The same application can also be built using the standalone [S2I](https://github.com/openshift/source-to-image) application on systems that have it available: +**Accessing the application:** +``` +$ oc get pods +$ oc exec -- curl 127.0.0.1:8080 +``` - ``` - $ s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.6/test/setup-test-app/ python-sample-app - ``` +Source-to-Image framework and scripts +------------------------------------- +This image supports the [Source-to-Image](https://docs.openshift.com/container-platform/4.14/openshift_images/create-images.html#images-create-s2i_create-images) +(S2I) strategy in OpenShift. The Source-to-Image is an OpenShift framework +which makes it easy to write images that take application source code as +an input, use a builder image like this Python container image, and produce +a new image that runs the assembled application as an output. + +To support the Source-to-Image framework, important scripts are included in the builder image: + +* The `/usr/libexec/s2i/assemble` script inside the image is run to produce a new image with the application artifacts. +The script takes sources of a given application and places them into appropriate directories inside the image. +It utilizes some common patterns in Perl application development (see the **Environment variables** section below). +* The `/usr/libexec/s2i/run` script is set as the default command in the resulting container image (the new image with the application artifacts). +It runs your application according to settings in `APP_MODULE`, `APP_FILE` or `APP_SCRIPT` environment variables or it tries to detect the best +way automatically. + +Building an application using a Dockerfile +------------------------------------------ +Compared to the Source-to-Image strategy, using a Dockerfile is a more +flexible way to build a Python container image with an application. +Use a Dockerfile when Source-to-Image is not sufficiently flexible for you or +when you build the image outside of the OpenShift environment. + +To use the Python image in a Dockerfile, follow these steps: +#### 1. Pull a base builder image to build on -Where `` is the s2i-python image you [downloaded from RHEL, Centos or Fedora registry](../README.md#Download) or [built](../README.md#Build) from these sources. For example ubi8/python-36, centos/python-36-centos7 or f31/python3. +``` +podman pull registry.access.redhat.com/ubi8/python-36 +``` -**Accessing the application:** +#### 2. Pull and application code + +An example application available at https://github.com/sclorg/django-ex.git is used here. Feel free to clone the repository for further experiments. +You can also take a look at code examples in s2i-python-container repository: https://github.com/sclorg/s2i-python-container/tree/master/examples + +``` +git clone https://github.com/sclorg/django-ex.git app-src ``` -$ curl 127.0.0.1:8080 + +#### 3. Prepare an application inside a container + +This step usually consists of at least these parts: + +* putting the application source into the container +* installing the dependencies +* setting the default command in the resulting image + +For all these three parts, users can either setup all manually and use commands `python` and `pip` explicitly in the Dockerfile, +or users can use the Source-to-Image scripts inside the image. + +The manual way comes with the highest level of flexibility but requires you to know how to work +with modules or software collections manually, how to setup virtual environment with the right version +of Python and many more. On the other hand, using Source-to-Image scripts makes your Dockerfile +prepared for a future flawless switch to a newer or different platform. + +To use the Source-to-Image scripts and build an image using a Dockerfile, create a Dockerfile with this content: + +``` +FROM registry.access.redhat.com/ubi8/python-36 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you decide not to use the Source-to-Image scripts, you will need to manually tailor the Dockerfile to your application and its needs. +Example Dockerfile for a simple Django application: + +``` +FROM registry.access.redhat.com/ubi8/python-36 + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 +``` + +#### 4. Build a new image from a Dockerfile prepared in the previous step + +``` +podman build -t python-app . +``` + +#### 5. Run the resulting image with final application + +``` +podman run -d python-app ``` Environment variables @@ -115,9 +219,9 @@ file inside your source code repository. 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_SETUP_PY_PROCESSING** +* **DISABLE_SETUP_PY_PROCESSING** / **DISABLE_PYPROJECT_TOML_PROCESSING** - Set this to a non-empty value to skip processing of setup.py script if you + Set this to a non-empty value to skip processing of setup.{py,cfg} or pyproject.toml script if you use `-e .` in requirements.txt to trigger its processing or you don't want your application to be installed into site-packages directory. @@ -127,6 +231,7 @@ file inside your source code repository. the higher-level Python packaging tool, to manage dependencies of the application. This should be used only if your project contains properly formated Pipfile and Pipfile.lock. + * **PIN_PIPENV_VERSION** Set this variable together with `ENABLE_PIPENV` to use a specific version of Pipenv. @@ -158,12 +263,18 @@ file inside your source code repository. the custom index, the container will try to install/update them from upstream PyPI afterwards. +* **PORT** + + HTTP(S) port your application should listen on. The default is 8080. + `PORT` is used only for Django development server and for Gunicorn + with the default configutation (no `APP_CONFIG` or `GUNICORN_CMD_ARGS` specified). + * **UPGRADE_PIP_TO_LATEST** Set this variable to a non-empty value to have the 'pip' program and related python packages (setuptools and wheel) 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. + before any Python packages are installed. If not set, the container will use + the stable pip version this container was built with, taken from a recent Fedora release. * **WEB_CONCURRENCY** @@ -196,7 +307,7 @@ However, if these files exist they will affect the behavior of the build process Configures various aspects of the project, including installation of dependencies, as documented - [here](https://packaging.python.org/en/latest/distributing.html#setup-py). + [here](https://packaging.python.org/guides/distributing-packages-using-setuptools/?highlight=distributing#setup-py). For most projects, it is sufficient to simply use `requirements.txt` or `Pipfile`. Set `DISABLE_SETUP_PY_PROCESSING` environment variable to true in order to skip processing of this file. @@ -221,6 +332,9 @@ following ways, in precedence order: If you have both Django and Gunicorn in your requirements, your Django project will automatically be served using Gunicorn. + The default setting for Gunicorn (`--bind=0.0.0.0:$PORT --access-logfile=-`) is applied + only if both `$APP_CONFIG` and `$GUNICORN_CMD_ARGS` are not defined. + * **Django development server** If you have Django in your requirements but don't have Gunicorn, then your @@ -244,7 +358,7 @@ following ways, in precedence order: application. Hot deploy ---------------------- +---------- If you are using Django, hot deploy will work out of the box. @@ -255,7 +369,7 @@ 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 podman's (or docker's) -[exec](https://github.com/containers/libpod/blob/master/docs/podman-exec.1.md) command: +[exec](https://github.com/containers/podman/blob/main/docs/source/markdown/podman-exec.1.md) command: ``` podman exec -it /bin/bash @@ -269,5 +383,6 @@ See also -------- Dockerfile and other sources are available on https://github.com/sclorg/s2i-python-container. In that repository you also can find another versions of Python environment Dockerfiles. -Dockerfile for CentOS is called `Dockerfile`, Dockerfile for RHEL7 is called `Dockerfile.rhel7`, -for RHEL8 it's `Dockerfile.rhel8` and the Fedora Dockerfile is called `Dockerfile.fedora`. +Dockerfile for RHEL8 is called `Dockerfile.rhel8`, for RHEL9 it's `Dockerfile.rhel9`, +for CentOS Stream 9 it's `Dockerfile.c9s`, for CentOS Stream 10 it's `Dockerfile.c10s`, +and the Fedora Dockerfile is called `Dockerfile.fedora`. diff --git a/3.6/cccp.yml b/3.6/cccp.yml deleted file mode 100644 index 597341fb..00000000 --- a/3.6/cccp.yml +++ /dev/null @@ -1,3 +0,0 @@ -# This is for the purpose of building this container -# on the centos container pipeline. -job-id: python-36-centos7 diff --git a/3.6/root/opt/app-root/etc/generate_container_user b/3.6/root/opt/app-root/etc/generate_container_user index 43ee7ecb..a7fd74d6 100644 --- a/3.6/root/opt/app-root/etc/generate_container_user +++ b/3.6/root/opt/app-root/etc/generate_container_user @@ -9,7 +9,7 @@ if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"1001" ]; then 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 + echo "default:x:${USER_ID}:${GROUP_ID}:Default Application User:${HOME}:/bin/bash" >> $NSS_WRAPPER_PASSWD export NSS_WRAPPER_PASSWD export NSS_WRAPPER_GROUP diff --git a/3.6/root/opt/app-root/etc/scl_enable b/3.6/root/opt/app-root/etc/scl_enable deleted file mode 100644 index ea3d45db..00000000 --- a/3.6/root/opt/app-root/etc/scl_enable +++ /dev/null @@ -1,9 +0,0 @@ -# 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 -if head "/etc/redhat-release" | grep -q "^CentOS Linux release 7" || \ - head "/etc/redhat-release" | grep -q "^Red Hat Enterprise Linux\( Server\)\? release 7"; then - source scl_source enable httpd24 $NODEJS_SCL rh-python36 -fi -source /opt/app-root/bin/activate diff --git a/3.6/root/opt/wheels b/3.6/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.6/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.6/s2i/bin/assemble b/3.6/s2i/bin/assemble index 32934e50..a2206292 100755 --- a/3.6/s2i/bin/assemble +++ b/3.6/s2i/bin/assemble @@ -57,20 +57,26 @@ mv /tmp/src/* "$HOME" # set permissions for any installed artifacts fix-permissions /opt/app-root -P -# We have to first upgrade pip to at least 19.3 because: -# * pip < 9 does not support different packages' versions for Python 2/3 -# * pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 wheels +# Python <3.7 only code, Python 3.7+ uses bundled wheel from Fedora +# We have to first upgrade pip to at least 19.3 because +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels # support platforms like ppc64le, aarch64 or armv7 -echo "---> Upgrading pip to version 19.3.1 ..." -if ! pip install -U "pip==19.3.1"; then - echo "WARNING: Installation of 'pip==19.3.1' failed, trying again from official PyPI with pip --isolated install" - pip install --isolated -U "pip==19.3.1" +echo "---> Upgrading pip to latest version supported for Python $PYTHON_VERSION ..." +if ! pip install -U "pip"; then + echo "WARNING: Installation of 'pip' failed, trying again from official PyPI with pip --isolated install" + pip install --isolated -U "pip" +fi + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini fi if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then - echo "---> Upgrading pip to latest version ..." + echo "---> Upgrading pip, setuptools and wheel to latest version ..." if ! pip install -U pip setuptools wheel; then - echo "WARNING: Installation of the latest pip,setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" pip install --isolated -U pip setuptools wheel fi fi @@ -98,8 +104,13 @@ elif [[ -f requirements.txt ]]; then pip install -r requirements.txt fi -if [[ -f setup.py && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then - echo "---> Installing application ..." +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." pip install . fi diff --git a/3.6/s2i/bin/run b/3.6/s2i/bin/run index ee1e1d3b..7003f038 100755 --- a/3.6/s2i/bin/run +++ b/3.6/s2i/bin/run @@ -43,30 +43,54 @@ 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 + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + APP_HOME=$(readlink -f "${APP_HOME:-.}") # Change the working directory to APP_HOME PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" cd "$APP_HOME" -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" +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 fi - maybe_run_in_init_wrapper "$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) ..." - maybe_run_in_init_wrapper python "$APP_FILE" -else - test -n "$app_file_check" && (>&2 echo "ERROR: file '$app_file_check' not found.") && exit 1 +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi fi # Look for 'manage.py' in the current directory @@ -82,6 +106,11 @@ if should_migrate; then fi fi +# If not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + if is_gunicorn_installed; then setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) # Look for wsgi module in the current directory @@ -94,17 +123,27 @@ if is_gunicorn_installed; then 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" + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" fi fi if is_django_installed; then if [[ -f "$manage_file" ]]; then - echo "---> Serving application with 'manage.py runserver' ..." + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." echo "WARNING: this is NOT a recommended way to run you application in production!" echo "Consider using gunicorn or some other production web server." - maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:8080 + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT else echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." echo "Skipped 'python manage.py runserver'." diff --git a/3.6/s2i/bin/usage b/3.6/s2i/bin/usage index 67ed3455..0d2e8ef0 100755 --- a/3.6/s2i/bin/usage +++ b/3.6/s2i/bin/usage @@ -6,13 +6,13 @@ NAMESPACE=centos cat < -- curl 127.0.0.1:8080 EOF diff --git a/3.6/test/__init__.py b/3.6/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.6/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/3.6/test/app-module-test-app b/3.6/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.6/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/3.6/test/conftest.py b/3.6/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.6/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.6/test/django-different-port-test-app b/3.6/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.6/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/3.6/test/from-dockerfile/Dockerfile.tpl b/3.6/test/from-dockerfile/Dockerfile.tpl new file mode 120000 index 00000000..ba88ccc4 --- /dev/null +++ b/3.6/test/from-dockerfile/Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/Dockerfile.tpl \ No newline at end of file diff --git a/3.6/test/from-dockerfile/Dockerfile_no_s2i.tpl b/3.6/test/from-dockerfile/Dockerfile_no_s2i.tpl new file mode 100644 index 00000000..da2f43ff --- /dev/null +++ b/3.6/test/from-dockerfile/Dockerfile_no_s2i.tpl @@ -0,0 +1,16 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 diff --git a/3.6/test/gunicorn-config-different-port-test-app b/3.6/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.6/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.6/test/gunicorn-different-port-test-app b/3.6/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.6/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.6/test/gunicorn-python-configfile-different-port-test-app b/3.6/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.6/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/3.6/test/micropipenv-test-app/Pipfile.lock b/3.6/test/micropipenv-test-app/Pipfile.lock index dc32658d..9102b17b 100644 --- a/3.6/test/micropipenv-test-app/Pipfile.lock +++ b/3.6/test/micropipenv-test-app/Pipfile.lock @@ -18,10 +18,10 @@ "default": { "certifi": { "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" + "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", + "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" ], - "version": "==2019.9.11" + "version": "==2020.12.5" }, "chardet": { "hashes": [ @@ -36,10 +36,11 @@ }, "gunicorn": { "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" + "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", + "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" ], - "version": "==19.9.0" + "markers": "python_version >= '3.5'", + "version": "==20.1.0" }, "idna": { "hashes": [ @@ -56,100 +57,107 @@ "index": "pypi", "version": "==2.20.0" }, + "testapp": { + "editable": true, + "path": "." + }, "urllib3": { "hashes": [ "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'", "version": "==1.24.3" } }, "develop": { - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "version": "==1.3.0" - }, "attrs": { "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "version": "==19.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.3.0" }, "importlib-metadata": { "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" + "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581", + "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d" ], - "markers": "python_version < '3.8'", - "version": "==0.23" + "markers": "python_version < '3.8' and python_version < '3.8'", + "version": "==4.0.1" }, - "more-itertools": { + "iniconfig": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" ], - "version": "==7.2.0" + "version": "==1.1.1" }, "packaging": { "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" + "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", + "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" ], - "version": "==19.2" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.9" }, "pluggy": { "hashes": [ - "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", - "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], - "version": "==0.13.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.1" }, "py": { "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", + "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" ], - "version": "==1.8.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.10.0" }, "pyparsing": { "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "version": "==2.4.2" + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.4.7" }, "pytest": { "hashes": [ - "sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", - "sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4" + "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", + "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" ], "index": "pypi", - "version": "==5.2.2" + "version": "==6.2.3" }, - "six": { + "toml": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "version": "==1.12.0" + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.2" }, - "wcwidth": { + "typing-extensions": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" ], - "version": "==0.1.7" + "markers": "python_version < '3.8'", + "version": "==3.10.0.0" }, "zipp": { "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", + "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" ], - "version": "==0.6.0" + "markers": "python_version >= '3.6'", + "version": "==3.4.1" } } } diff --git a/3.6/test/micropipenv-test-app/testapp.py b/3.6/test/micropipenv-test-app/testapp.py index a9193eec..fe200352 100644 --- a/3.6/test/micropipenv-test-app/testapp.py +++ b/3.6/test/micropipenv-test-app/testapp.py @@ -1,7 +1,12 @@ +import sys import requests def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) - assert requests.__version__ == '2.20.0' + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' return [b"Hello from gunicorn WSGI application!"] diff --git a/3.6/test/pin-pipenv-version-test-app b/3.6/test/pin-pipenv-version-test-app deleted file mode 120000 index ef18267b..00000000 --- a/3.6/test/pin-pipenv-version-test-app +++ /dev/null @@ -1 +0,0 @@ -../../examples/pin-pipenv-version-test-app \ No newline at end of file diff --git a/3.6/test/pin-pipenv-version-test-app/.s2i/environment b/3.6/test/pin-pipenv-version-test-app/.s2i/environment new file mode 100644 index 00000000..9bfe6709 --- /dev/null +++ b/3.6/test/pin-pipenv-version-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2021.5.29 diff --git a/3.6/test/pin-pipenv-version-test-app/app.sh b/3.6/test/pin-pipenv-version-test-app/app.sh new file mode 100755 index 00000000..4334c760 --- /dev/null +++ b/3.6/test/pin-pipenv-version-test-app/app.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.6/test/pin-pipenv-version-test-app/requirements.txt b/3.6/test/pin-pipenv-version-test-app/requirements.txt new file mode 100644 index 00000000..66d42051 --- /dev/null +++ b/3.6/test/pin-pipenv-version-test-app/requirements.txt @@ -0,0 +1,2 @@ +uWSGI +Flask diff --git a/3.6/test/pin-pipenv-version-test-app/wsgi.py b/3.6/test/pin-pipenv-version-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.6/test/pin-pipenv-version-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() diff --git a/3.6/test/pipenv-test-app/Pipfile.lock b/3.6/test/pipenv-test-app/Pipfile.lock index dc32658d..9102b17b 100644 --- a/3.6/test/pipenv-test-app/Pipfile.lock +++ b/3.6/test/pipenv-test-app/Pipfile.lock @@ -18,10 +18,10 @@ "default": { "certifi": { "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" + "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", + "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" ], - "version": "==2019.9.11" + "version": "==2020.12.5" }, "chardet": { "hashes": [ @@ -36,10 +36,11 @@ }, "gunicorn": { "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" + "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", + "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" ], - "version": "==19.9.0" + "markers": "python_version >= '3.5'", + "version": "==20.1.0" }, "idna": { "hashes": [ @@ -56,100 +57,107 @@ "index": "pypi", "version": "==2.20.0" }, + "testapp": { + "editable": true, + "path": "." + }, "urllib3": { "hashes": [ "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'", "version": "==1.24.3" } }, "develop": { - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "version": "==1.3.0" - }, "attrs": { "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "version": "==19.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.3.0" }, "importlib-metadata": { "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" + "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581", + "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d" ], - "markers": "python_version < '3.8'", - "version": "==0.23" + "markers": "python_version < '3.8' and python_version < '3.8'", + "version": "==4.0.1" }, - "more-itertools": { + "iniconfig": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" ], - "version": "==7.2.0" + "version": "==1.1.1" }, "packaging": { "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" + "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", + "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" ], - "version": "==19.2" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.9" }, "pluggy": { "hashes": [ - "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", - "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], - "version": "==0.13.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.1" }, "py": { "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", + "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" ], - "version": "==1.8.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.10.0" }, "pyparsing": { "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "version": "==2.4.2" + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.4.7" }, "pytest": { "hashes": [ - "sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", - "sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4" + "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", + "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" ], "index": "pypi", - "version": "==5.2.2" + "version": "==6.2.3" }, - "six": { + "toml": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "version": "==1.12.0" + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.2" }, - "wcwidth": { + "typing-extensions": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" ], - "version": "==0.1.7" + "markers": "python_version < '3.8'", + "version": "==3.10.0.0" }, "zipp": { "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", + "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" ], - "version": "==0.6.0" + "markers": "python_version >= '3.6'", + "version": "==3.4.1" } } } diff --git a/3.6/test/pipenv-test-app/testapp.py b/3.6/test/pipenv-test-app/testapp.py index a9193eec..fe200352 100644 --- a/3.6/test/pipenv-test-app/testapp.py +++ b/3.6/test/pipenv-test-app/testapp.py @@ -1,7 +1,12 @@ +import sys import requests def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) - assert requests.__version__ == '2.20.0' + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' return [b"Hello from gunicorn WSGI application!"] diff --git a/3.6/test/poetry-src-layout-test-app b/3.6/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.6/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.6/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.6/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..9bfe6709 --- /dev/null +++ b/3.6/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2021.5.29 diff --git a/3.6/test/pyuwsgi-pipenv-test-app/app.sh b/3.6/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.6/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.6/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.6/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.6/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.6/test/pyuwsgi-pipenv-test-app/wsgi.py b/3.6/test/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.6/test/pyuwsgi-pipenv-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() diff --git a/3.6/test/run b/3.6/test/run index 45e09ce7..6793f5f8 100755 --- a/3.6/test/run +++ b/3.6/test/run @@ -6,24 +6,51 @@ # 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 WEB_APPS=({standalone,setup,setup-requirements,django,numpy,app-home,npm-virtualenv-uwsgi,locale,mod-wsgi,pipenv,pipenv-and-micropipenv-should-fail,pin-pipenv-version,micropipenv,micropipenv-requirements}-test-app) +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,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${FULL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) # TODO: Make command compatible for Mac users -test_dir="$(readlink -zf $(dirname "${BASH_SOURCE[0]}"))" -image_dir=$(readlink -zf ${test_dir}/..) +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile +" if [[ -z $VERSION ]]; then echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 exit 1 fi -IMAGE_NAME=${IMAGE_NAME:-centos/python-${VERSION//./}-centos7} +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} . test/test-lib.sh -# TODO: This should be part of the image metadata -test_port=8080 - info() { echo -e "\n\e[1m[INFO] $@\e[0m\n" } @@ -49,7 +76,7 @@ run_s2i_build() { prepare() { if ! image_exists ${IMAGE_NAME}; then echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." - exit 1 + return 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. @@ -65,15 +92,6 @@ 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 @@ -81,23 +99,6 @@ cleanup() { fi rm -rf ${test_dir}/${1}/.git } - -check_result() { - # positive test & non-zero exit status = ERROR - # negative test & zero exit status = ERROR - local result="$1" - local type=${2:-positive} # if not defined, we expect possitive test type (zero exit code) - if [[ "$type" == "positive" && "$result" != "0" ]]; then - info "TEST FAILED (${type}), EXPECTED:0 GOT:${result}" - cleanup - exit $result - elif [[ "$type" == "negative" && "$result" == "0" ]]; then - info "TEST FAILED (${type}), EXPECTED: non-zero GOT:${result}" - cleanup - exit 1 - fi -} - wait_for_cid() { local max_attempts=10 local sleep_time=1 @@ -118,7 +119,7 @@ test_s2i_usage() { test_docker_run_usage() { info "Testing 'docker run' usage ..." - docker run ${IMAGE_NAME} &>/dev/null + docker run --rm ${IMAGE_NAME} &>/dev/null } test_scl_usage() { @@ -126,7 +127,7 @@ test_scl_usage() { local expected="$2" local cid_file="$3" - info "Testing the image SCL enable" + info "Testing command availability in the image" 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}'" @@ -166,7 +167,7 @@ test_connection() { } test_application() { - local cid_file=$(mktemp -u --suffix=.cid) + local cid_file="$CID_FILE_DIR"/"$(mktemp -u -p . --suffix .cid)" # Verify that the HTTP connection can be established to test application container run_test_application & @@ -174,70 +175,137 @@ test_application() { wait_for_cid # Some test apps have tests in their startup code so we have to check # that the container starts at all - check_result $? + ct_check_testcase_result $? - test_scl_usage "python --version" "Python $VERSION." "${cid_file}" - check_result $? + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? + ct_check_testcase_result $? test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? + ct_check_testcase_result $? test_connection - check_result $? - cleanup_app + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") } -test_latest_imagestreams() { - local result=1 - info "Testing the latest version in imagestreams" - # Switch to root directory of a container - pushd "${test_dir}/../.." >/dev/null || return 1 - ct_check_latest_imagestreams - result=$? - popd >/dev/null || return 1 - check_result $result +test_from_dockerfile(){ + info "Test from Dockerfile" + django_example_repo_url="https://github.com/sclorg/django-ex.git@2.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? } -# Since we built the candidate image locally, we don't want S2I attempt to pull -# it from Docker hub -s2i_args="--pull-policy=never" +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" -# Verify the 'usage' script is working properly when running the base image with 's2i usage ...' -test_s2i_usage -check_result $? + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. -# Verify the 'usage' script is working properly when running the base image with 'docker run ...' -test_docker_run_usage -check_result $? + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? -# For debugging purposes, this script can be run with one or more arguments -# those arguments list is a sub-set of values in the WEB_APPS array defined above -# Example: ./run app-home-test-app pipenv-test-app -for app in ${@:-${WEB_APPS[@]}}; do - prepare ${app} - run_s2i_build ${app} - RESULT=$? - if [[ "$app" == *"-should-fail-"* ]]; then - # Tests with '-should-fail-' in their name should fail during a build, expecting non-zero exit status - check_result $RESULT "negative" - continue + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? else - check_result $RESULT + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." fi - # test application with default user - test_application +} +test_application_with_user() { # test application with random user CONTAINER_ARGS="--user 12345" test_application +} + +test_application_enable_init_wrapper() { # test application with init wrapper CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue - test_latest_imagestreams + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" - info "All tests for the ${app} finished successfully." cleanup ${app} done -info "All tests finished successfully." +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.6/test/run-openshift b/3.6/test/run-openshift deleted file mode 120000 index ec13e4a1..00000000 --- a/3.6/test/run-openshift +++ /dev/null @@ -1 +0,0 @@ -../../test/run-openshift \ No newline at end of file diff --git a/3.6/test/run-openshift-pytest b/3.6/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.6/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.6/test/run-pytest b/3.6/test/run-pytest new file mode 100755 index 00000000..9810fe89 --- /dev/null +++ b/3.6/test/run-pytest @@ -0,0 +1,16 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +if python3 -c 'import sys; sys.exit(0 if sys.version_info < (3,13) else 1)'; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.6/test/setup-cfg-test-app b/3.6/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.6/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/3.6/test/standalone-custom-pypi-index-test-app b/3.6/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.6/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/3.6/test/test-lib-openshift.sh b/3.6/test/test-lib-openshift.sh deleted file mode 120000 index 4f9f2996..00000000 --- a/3.6/test/test-lib-openshift.sh +++ /dev/null @@ -1 +0,0 @@ -../../common/test-lib-openshift.sh \ No newline at end of file diff --git a/3.6/test/test-lib-python.sh b/3.6/test/test-lib-python.sh deleted file mode 120000 index 0fa72132..00000000 --- a/3.6/test/test-lib-python.sh +++ /dev/null @@ -1 +0,0 @@ -../../test/test-lib-python.sh \ No newline at end of file diff --git a/3.6/test/test_container_application.py b/3.6/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.6/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.6/test/test_container_basics.py b/3.6/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.6/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.6/test/test_ocp_deploy_templates.py b/3.6/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.6/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.6/test/test_ocp_helm_python_django_app.py b/3.6/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.6/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.6/test/test_ocp_helm_python_django_psql_persistent.py b/3.6/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.6/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.6/test/test_ocp_helm_python_imagestreams.py b/3.6/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.6/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.6/test/test_ocp_imagestreams_quickstart.py b/3.6/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.6/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.6/test/test_ocp_python_ex_standalone.py b/3.6/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.6/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.6/test/test_ocp_python_ex_template.py b/3.6/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.6/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file diff --git a/3.7/cccp.yml b/3.7/cccp.yml deleted file mode 100644 index a594ef3d..00000000 --- a/3.7/cccp.yml +++ /dev/null @@ -1,3 +0,0 @@ -# This is for the purpose of building this container -# on the centos container pipeline. -job-id: python-37-centos7 diff --git a/3.7/root/opt/app-root/etc/generate_container_user b/3.7/root/opt/app-root/etc/generate_container_user deleted file mode 100644 index 43ee7ecb..00000000 --- a/3.7/root/opt/app-root/etc/generate_container_user +++ /dev/null @@ -1,19 +0,0 @@ -# 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/3.7/root/opt/app-root/etc/scl_enable b/3.7/root/opt/app-root/etc/scl_enable deleted file mode 100644 index 40016b19..00000000 --- a/3.7/root/opt/app-root/etc/scl_enable +++ /dev/null @@ -1,9 +0,0 @@ -# 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 -if head "/etc/redhat-release" | grep -q "^CentOS Linux release 7" || \ - head "/etc/redhat-release" | grep -q "^Red Hat Enterprise Linux\( Server\)\? release 7"; then - source scl_source enable httpd24 $NODEJS_SCL rh-python37 -fi -source /opt/app-root/bin/activate diff --git a/3.7/s2i/bin/usage b/3.7/s2i/bin/usage deleted file mode 100755 index b1c74482..00000000 --- a/3.7/s2i/bin/usage +++ /dev/null @@ -1,18 +0,0 @@ -#!/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 ..." - ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} -} - -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() { - # positive test & non-zero exit status = ERROR - # negative test & zero exit status = ERROR - local result="$1" - local type=${2:-positive} # if not defined, we expect possitive test type (zero exit code) - if [[ "$type" == "positive" && "$result" != "0" ]]; then - info "TEST FAILED (${type}), EXPECTED:0 GOT:${result}" - cleanup - exit $result - elif [[ "$type" == "negative" && "$result" == "0" ]]; then - info "TEST FAILED (${type}), EXPECTED: non-zero GOT:${result}" - cleanup - exit 1 - fi -} - -wait_for_cid() { - local max_attempts=10 - local sleep_time=1 - local attempt=1 - info "Waiting for application container to start $CONTAINER_ARGS ..." - while [ $attempt -le $max_attempts ]; do - [ -f $cid_file ] && [ -s $cid_file ] && return 0 - attempt=$(( $attempt + 1 )) - sleep $sleep_time - done - return 1 -} - -test_s2i_usage() { - info "Testing 's2i usage' ..." - ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/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 - # Some test apps have tests in their startup code so we have to check - # that the container starts at all - check_result $? - - test_scl_usage "python --version" "Python $VERSION." "${cid_file}" - check_result $? - test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? - test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? - test_connection - check_result $? - cleanup_app -} - -test_latest_imagestreams() { - local result=1 - info "Testing the latest version in imagestreams" - # Switch to root directory of a container - pushd "${test_dir}/../.." >/dev/null || return 1 - ct_check_latest_imagestreams - result=$? - popd >/dev/null || return 1 - check_result $result -} - -# Since we built the candidate image locally, we don't want S2I attempt to pull -# it from Docker hub -s2i_args="--pull-policy=never" - -# 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 debugging purposes, this script can be run with one or more arguments -# those arguments list is a sub-set of values in the WEB_APPS array defined above -# Example: ./run app-home-test-app pipenv-test-app -for app in ${@:-${WEB_APPS[@]}}; do - prepare ${app} - run_s2i_build ${app} - RESULT=$? - if [[ "$app" == *"-should-fail-"* ]]; then - # Tests with '-should-fail-' in their name should fail during a build, expecting non-zero exit status - check_result $RESULT "negative" - continue - else - check_result $RESULT - fi - - # test application with default user - test_application - - # test application with random user - CONTAINER_ARGS="--user 12345" test_application - - # test application with init wrapper - CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application - - test_latest_imagestreams - - info "All tests for the ${app} finished successfully." - cleanup ${app} -done - -info "All tests finished successfully." diff --git a/3.7/test/run-openshift b/3.7/test/run-openshift deleted file mode 120000 index ec13e4a1..00000000 --- a/3.7/test/run-openshift +++ /dev/null @@ -1 +0,0 @@ -../../test/run-openshift \ No newline at end of file diff --git a/3.7/test/test-lib-openshift.sh b/3.7/test/test-lib-openshift.sh deleted file mode 120000 index 4f9f2996..00000000 --- a/3.7/test/test-lib-openshift.sh +++ /dev/null @@ -1 +0,0 @@ -../../common/test-lib-openshift.sh \ No newline at end of file diff --git a/3.7/test/test-lib-python.sh b/3.7/test/test-lib-python.sh deleted file mode 120000 index 0fa72132..00000000 --- a/3.7/test/test-lib-python.sh +++ /dev/null @@ -1 +0,0 @@ -../../test/test-lib-python.sh \ No newline at end of file diff --git a/3.8/Dockerfile b/3.8/Dockerfile deleted file mode 100644 index 7d292d84..00000000 --- a/3.8/Dockerfile +++ /dev/null @@ -1,68 +0,0 @@ -# This image provides a Python 3.8 environment you can use to run your Python -# applications. -FROM centos/s2i-base-centos7 - -EXPOSE 8080 - -ENV PYTHON_VERSION=3.8 \ - 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 SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ - DESCRIPTION="Python $PYTHON_VERSION available as 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.8" \ - io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python38,python-38,rh-python38" \ - com.redhat.component="python38-container" \ - name="centos/python-38-centos7" \ - version="1" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.8/test/setup-test-app/ centos/python-38-centos7 python-sample-app" \ - maintainer="SoftwareCollections.org " - -RUN INSTALL_PKGS="rh-python38 rh-python38-python-devel rh-python38-python-setuptools rh-python38-python-pip nss_wrapper \ - httpd24 httpd24-httpd-devel httpd24-mod_ssl httpd24-mod_auth_kerb httpd24-mod_ldap \ - httpd24-mod_session atlas-devel gcc-gfortran libffi-devel libtool-ltdl enchant" && \ - yum install -y centos-release-scl && \ - yum -y --setopt=tsflags=nodocs install --enablerepo=centosplus $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - # Remove centos-logos (httpd dependency) to keep image size smaller. - rpm -e --nodeps centos-logos && \ - yum -y clean all --enablerepo='*' - -# 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. -# - 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 source scl_source enable rh-python38 && \ - python3.8 -m venv ${APP_ROOT} && \ - chown -R 1001:0 ${APP_ROOT} && \ - fix-permissions ${APP_ROOT} -P && \ - rpm-file-permissions - -USER 1001 - -# Set the default CMD to print the usage of the language image. -CMD $STI_SCRIPTS_PATH/usage diff --git a/3.8/Dockerfile.rhel7 b/3.8/Dockerfile.rhel7 deleted file mode 100644 index 0ea57d33..00000000 --- a/3.8/Dockerfile.rhel7 +++ /dev/null @@ -1,70 +0,0 @@ -# This image provides a Python 3.8 environment you can use to run your Python -# applications. -FROM rhscl/s2i-base-rhel7:1 - -EXPOSE 8080 - -ENV PYTHON_VERSION=3.8 \ - 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 SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ - DESCRIPTION="Python $PYTHON_VERSION available as 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.8" \ - io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python38,python-38,rh-python38" \ - com.redhat.component="rh-python38-container" \ - name="rhscl/python-38-rhel7" \ - version="1" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.8/test/setup-test-app/ rhscl/python-38-rhel7 python-sample-app" \ - com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ - maintainer="SoftwareCollections.org " - -RUN INSTALL_PKGS="rh-python38 rh-python38-python-devel rh-python38-python-setuptools rh-python38-python-pip nss_wrapper \ - httpd24 httpd24-httpd-devel httpd24-mod_ssl httpd24-mod_auth_kerb httpd24-mod_ldap \ - httpd24-mod_session atlas-devel gcc-gfortran libffi-devel libtool-ltdl enchant" && \ - yum install -y yum-utils && \ - prepare-yum-repositories rhel-server-rhscl-7-rpms && \ - yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - # Remove redhat-logos (httpd dependency) to keep image size smaller. - rpm -e --nodeps redhat-logos && \ - yum -y clean all --enablerepo='*' - -# 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. -# - 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 source scl_source enable rh-python38 && \ - python3.8 -m venv ${APP_ROOT} && \ - chown -R 1001:0 ${APP_ROOT} && \ - fix-permissions ${APP_ROOT} -P && \ - rpm-file-permissions - -USER 1001 - -# Set the default CMD to print the usage of the language image. -CMD $STI_SCRIPTS_PATH/usage diff --git a/3.8/Dockerfile.rhel8 b/3.8/Dockerfile.rhel8 deleted file mode 100644 index fd9520ae..00000000 --- a/3.8/Dockerfile.rhel8 +++ /dev/null @@ -1,76 +0,0 @@ -# This image provides a Python 3.8 environment you can use to run your Python -# applications. -FROM ubi8/s2i-base:1 - -EXPOSE 8080 - -ENV PYTHON_VERSION=3.8 \ - 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 - -# RHEL7 base images atomatically set these envvars to run scl_enable. RHEl8 -# images, however, don't as most images don't need SCLs any more. But we want -# to run it even on RHEL8, because we set the virtualenv environment as part of -# that script -ENV BASH_ENV=${APP_ROOT}/etc/scl_enable \ - ENV=${APP_ROOT}/etc/scl_enable \ - PROMPT_COMMAND=". ${APP_ROOT}/etc/scl_enable" - -ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ - DESCRIPTION="Python $PYTHON_VERSION available as 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.8" \ - io.openshift.expose-services="8080:http" \ - io.openshift.tags="builder,python,python38,python-38,rh-python38" \ - com.redhat.component="python-38-container" \ - name="ubi8/python-38" \ - version="1" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.8/test/setup-test-app/ ubi8/python-38 python-sample-app" \ - com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ - maintainer="SoftwareCollections.org " - -RUN INSTALL_PKGS="python38 python38-devel python38-setuptools python38-pip nss_wrapper \ - httpd httpd-devel mod_ssl mod_auth_gssapi mod_ldap \ - mod_session atlas-devel gcc-gfortran libffi-devel libtool-ltdl enchant" && \ - yum -y module enable python38:3.8 httpd:2.4 && \ - yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - # Remove redhat-logos-httpd (httpd dependency) to keep image size smaller. - rpm -e --nodeps redhat-logos-httpd && \ - yum -y clean all --enablerepo='*' - -# 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. -# - 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 \ - python3.8 -m venv ${APP_ROOT} && \ - chown -R 1001:0 ${APP_ROOT} && \ - fix-permissions ${APP_ROOT} -P && \ - rpm-file-permissions - -USER 1001 - -# Set the default CMD to print the usage of the language image. -CMD $STI_SCRIPTS_PATH/usage diff --git a/3.8/cccp.yml b/3.8/cccp.yml deleted file mode 100644 index 65d80eb9..00000000 --- a/3.8/cccp.yml +++ /dev/null @@ -1,3 +0,0 @@ -# This is for the purpose of building this container -# on the centos container pipeline. -job-id: python-38-centos7 diff --git a/3.8/root/opt/app-root/etc/generate_container_user b/3.8/root/opt/app-root/etc/generate_container_user deleted file mode 100644 index 43ee7ecb..00000000 --- a/3.8/root/opt/app-root/etc/generate_container_user +++ /dev/null @@ -1,19 +0,0 @@ -# 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/3.8/root/opt/app-root/etc/scl_enable b/3.8/root/opt/app-root/etc/scl_enable deleted file mode 100644 index fd98aa1c..00000000 --- a/3.8/root/opt/app-root/etc/scl_enable +++ /dev/null @@ -1,9 +0,0 @@ -# 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 -if head "/etc/redhat-release" | grep -q "^CentOS Linux release 7" || \ - head "/etc/redhat-release" | grep -q "^Red Hat Enterprise Linux\( Server\)\? release 7"; then - source scl_source enable httpd24 $NODEJS_SCL rh-python38 -fi -source /opt/app-root/bin/activate diff --git a/3.8/s2i/bin/usage b/3.8/s2i/bin/usage deleted file mode 100755 index 9caff407..00000000 --- a/3.8/s2i/bin/usage +++ /dev/null @@ -1,18 +0,0 @@ -#!/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 ..." - ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} -} - -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() { - # positive test & non-zero exit status = ERROR - # negative test & zero exit status = ERROR - local result="$1" - local type=${2:-positive} # if not defined, we expect possitive test type (zero exit code) - if [[ "$type" == "positive" && "$result" != "0" ]]; then - info "TEST FAILED (${type}), EXPECTED:0 GOT:${result}" - cleanup - exit $result - elif [[ "$type" == "negative" && "$result" == "0" ]]; then - info "TEST FAILED (${type}), EXPECTED: non-zero GOT:${result}" - cleanup - exit 1 - fi -} - -wait_for_cid() { - local max_attempts=10 - local sleep_time=1 - local attempt=1 - info "Waiting for application container to start $CONTAINER_ARGS ..." - while [ $attempt -le $max_attempts ]; do - [ -f $cid_file ] && [ -s $cid_file ] && return 0 - attempt=$(( $attempt + 1 )) - sleep $sleep_time - done - return 1 -} - -test_s2i_usage() { - info "Testing 's2i usage' ..." - ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/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 - # Some test apps have tests in their startup code so we have to check - # that the container starts at all - check_result $? - - test_scl_usage "python --version" "Python $VERSION." "${cid_file}" - check_result $? - test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? - test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? - test_connection - check_result $? - cleanup_app -} - -test_latest_imagestreams() { - local result=1 - info "Testing the latest version in imagestreams" - # Switch to root directory of a container - pushd "${test_dir}/../.." >/dev/null || return 1 - ct_check_latest_imagestreams - result=$? - popd >/dev/null || return 1 - check_result $result -} - -# Since we built the candidate image locally, we don't want S2I attempt to pull -# it from Docker hub -s2i_args="--pull-policy=never" - -# 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 debugging purposes, this script can be run with one or more arguments -# those arguments list is a sub-set of values in the WEB_APPS array defined above -# Example: ./run app-home-test-app pipenv-test-app -for app in ${@:-${WEB_APPS[@]}}; do - prepare ${app} - run_s2i_build ${app} - RESULT=$? - if [[ "$app" == *"-should-fail-"* ]]; then - # Tests with '-should-fail-' in their name should fail during a build, expecting non-zero exit status - check_result $RESULT "negative" - continue - else - check_result $RESULT - fi - - # test application with default user - test_application - - # test application with random user - CONTAINER_ARGS="--user 12345" test_application - - # test application with init wrapper - CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application - - test_latest_imagestreams - - info "All tests for the ${app} finished successfully." - cleanup ${app} -done - -info "All tests finished successfully." diff --git a/3.8/test/run-openshift b/3.8/test/run-openshift deleted file mode 120000 index ec13e4a1..00000000 --- a/3.8/test/run-openshift +++ /dev/null @@ -1 +0,0 @@ -../../test/run-openshift \ No newline at end of file diff --git a/3.8/test/test-lib-openshift.sh b/3.8/test/test-lib-openshift.sh deleted file mode 120000 index 4f9f2996..00000000 --- a/3.8/test/test-lib-openshift.sh +++ /dev/null @@ -1 +0,0 @@ -../../common/test-lib-openshift.sh \ No newline at end of file diff --git a/3.8/test/test-lib-python.sh b/3.8/test/test-lib-python.sh deleted file mode 120000 index 0fa72132..00000000 --- a/3.8/test/test-lib-python.sh +++ /dev/null @@ -1 +0,0 @@ -../../test/test-lib-python.sh \ No newline at end of file diff --git a/3.9-minimal/Dockerfile.c9s b/3.9-minimal/Dockerfile.c9s new file mode 100644 index 00000000..f5f2d5fc --- /dev/null +++ b/3.9-minimal/Dockerfile.c9s @@ -0,0 +1,104 @@ +FROM quay.io/centos/centos:stream9-minimal + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.9 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi9-python-39 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el9" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.9" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python39,python-39,rh-python39" \ + com.redhat.component="python-39-container" \ + name="sclorg/python-39-minimal-c9s" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.9-minimal/test/setup-test-app/ ubi9/python-39-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi9-python-39-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python39 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.9-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.9-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.9-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.9 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.9-minimal/README.md b/3.9-minimal/README.md new file mode 100644 index 00000000..11a225e4 --- /dev/null +++ b/3.9-minimal/README.md @@ -0,0 +1,125 @@ +Python 3.9 container image - minimal version +============================================ + +This container image is a special version of the [full Python 3.9 container image](https://github.com/sclorg/s2i-python-container/tree/master/3.9) +provided as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.9 applications. + +Because the minimal and full images work similarly, we document here only the differences and limitations +of the minimal container image. For the documentation of common features see the [full container image docs](https://github.com/sclorg/s2i-python-container/tree/master/3.9). + +The Python 3.9 minimal container image is currently considered a tech-preview and only available on quay.io. +The image is built on top of the [official CentOS Stream base containers](quay.io/centos/centos). + +To pull the Python 3.9 minimal container image to build on, run + +``` +podman pull quay.io/sclorg/python-39-minimal-c9s +``` + +Description +----------- + +The full container image is a universal base image to build your containerized applications on top of. However, its universal nature +means that the resulting containers it produces consume a lot of disk space. This is caused mainly by the fact that the image contains +npm, compilers, header files and some other packages one might need to install and deploy their applications. + +Because size does matter for us and our customers, we have prepared this minimal container image with very limited subset +of installed packages. There are no compilers, no header files, no npm etc and the yum package manager is replaced with a minimalistic +reimplementation called microdnf, making the resulting container images much smaller. This creates some limitations +but we provide ways to workaround them. + +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Limitations +----------- + +1. There is only a very limited subset of packages installed. They are choosen carefully to satisfy most of the Python apps but your app might have some special needs. +1. There is no npm and nodejs. +1. There are no compilers and header files. Installation from Python wheels should still work but compilation from a source code is not supported out of the box. + +In the next chapter, we provide three possible workarounds for the mentioned limitations of the minimal container image. + +Possible solutions for the limitations +-------------------------------------- + +### Use the full container image + +It's easy at that. If you don't want to write your own Dockerfile and disk space is not a problem, use +the full universal container image and you should be fine. + +### Build your own container image on top of the minimal container image + +Let's say that your application depends on uwsgi. uwsgi cannot be installed from Python wheel and has to be +compiled from source which requires some additional packages to be installed - namely gcc for the compilation +itself and python39-devel containing Python header files. + +To solve that problem, you can use all the pieces provided by the minimal container image and just add one more +step to install the missing dependencies: + +``` +FROM python-39-minimal + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src + +# Install packages necessary for compiling uwsgi from source +RUN microdnf install -y gcc python39-devel +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you do it this way, your problem with the missing packages is solved. But there is also one disadvantage: the resulting +runtime image contains unnecessary compiler and Python header files. How to solve this? Uninstalling them at the end +of the Dockerfile is not really a solution but we have one. Keep reading. + +### Build on full image, run on minimal image + +Did you know that you can copy files from one image to another one during a build? That's the feature we are gonna use now. +We use the full container image with all compilers and other usefull packages installed to build our app and its dependencies +and we then move the result including the whole virtual environemnt to the minimal container image. + +This app needs mod_wsgi and to install (compile it from source) it, we'll need: httpd-devel for header files, gcc and redhat-rpm-config +as a compiler and configuratuion and finally python39-devel containing Python header files. There is no need to install those packages +manually because the full container image already contains them. However, the application needs httpd as a runtime dependency +so we need to install it to the minimal container image as well. + +``` +# Part 1 - build + +FROM python-39 as builder + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the application's dependencies from PyPI +RUN /usr/libexec/s2i/assemble + +# Part 2 - deploy + +FROM python-39-minimal + +# Copy app sources together with the whole virtual environment from the builder image +COPY --from=builder $APP_ROOT $APP_ROOT + +# Install httpd package - runtime dependency of our application +USER 0 +RUN microdnf install -y httpd +USER 1001 + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +This way, the resulting container image does contain only necessary dependencies and it's much lighter. diff --git a/3.9-minimal/root/opt/app-root/etc/generate_container_user b/3.9-minimal/root/opt/app-root/etc/generate_container_user new file mode 100644 index 00000000..a7fd74d6 --- /dev/null +++ b/3.9-minimal/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}:/bin/bash" >> $NSS_WRAPPER_PASSWD + + export NSS_WRAPPER_PASSWD + export NSS_WRAPPER_GROUP + + LD_PRELOAD=libnss_wrapper.so + export LD_PRELOAD +fi diff --git a/3.9-minimal/root/opt/wheels b/3.9-minimal/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.9-minimal/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.9-minimal/root/usr/bin/cgroup-limits b/3.9-minimal/root/usr/bin/cgroup-limits new file mode 100755 index 00000000..6fa19153 --- /dev/null +++ b/3.9-minimal/root/usr/bin/cgroup-limits @@ -0,0 +1,192 @@ +#!/usr/libexec/platform-python + +""" +Script for parsing cgroup information + +This script will read some limits from the cgroup system and parse +them, printing out "VARIABLE=VALUE" on each line for every limit that is +successfully read. Output of this script can be directly fed into +bash's export command. Recommended usage from a bash script: + + set -o errexit + export_vars=$(cgroup-limits) ; export $export_vars + +Variables currently supported: + MAX_MEMORY_LIMIT_IN_BYTES + Maximum possible limit MEMORY_LIMIT_IN_BYTES can have. This is + currently constant value of 9223372036854775807. + MEMORY_LIMIT_IN_BYTES + Maximum amount of user memory in bytes. If this value is set + to the same value as MAX_MEMORY_LIMIT_IN_BYTES, it means that + there is no limit set. The value is taken from + /sys/fs/cgroup/memory/memory.limit_in_bytes for cgroups v1 + and from /sys/fs/cgroup/memory.max for cgroups v2 + NUMBER_OF_CORES + Number of CPU cores that can be used. If both the cpu and cpuset + controllers specify a limit, the controller with the lowest CPU + limit takes precedence. For the cpu controller, the value is + calculated from /sys/fs/cgroup/cpu/cpu.cfs_{quota,period}_us for + cgroups v1 and from /sys/fs/cgroup/cpuset.cpus.effective for cgroups v2. + For the cpuset controller, the value is taken from + /sys/fs/cgroup/cpuset/cpuset.cpus for cgroups v1 and from + /sys/fs/cgroup/cpuset.cpus.effective for cgroups v2 + NO_MEMORY_LIMIT + Set to "true" if MEMORY_LIMIT_IN_BYTES is so high that the caller + can act as if no memory limit was set. Undefined otherwise. + +Note about non-root containers: + + Per podman-run(1) man page, on some systems, changing the resource limits + may not be allowed for non-root users. For more details, see + https://github.com/containers/podman/blob/main/troubleshooting.md#26-running-containers-with-resource-limits-fails-with-a-permissions-error. + +How to test this script: + + Run a container as root and see whether the output of available memory + and CPUs match what is either available on the host or specified via + cgroups limits by the container runtime (podman). + + For example: + + # This should return NO_MEMORY_LIMIT=true, NUMBER_OF_CORES= + sudo podman run -ti --rm quay.io/fedora/s2i-core /usr/bin/cgroup-limits + + # This should return MEMORY_LIMIT_IN_BYTES=2147483648, NUMBER_OF_CORES=3 + # 3 CPUs despite --cpus 4 was given is correct, because the cpuset 2-4 means + # running on 3 concrete processors only, which is lower limit than --cpus + # and thus is preferred + sudo podman run -ti --rm --cpus 4 --cpuset-cpus=2-4 --memory 2G quay.io/fedora/s2i-core /usr/bin/cgroup-limits +""" + +from __future__ import division +from __future__ import print_function +import sys +import subprocess + + +def _read_file(path): + try: + with open(path, 'r') as f: + return f.read().strip() + except IOError: + return None + + +def get_memory_limit(): + """ + Read memory limit, in bytes. + """ + + limit = _read_file('/sys/fs/cgroup/memory/memory.limit_in_bytes') + # If first file does not exist, try cgroups v2 file + limit = limit or _read_file('/sys/fs/cgroup/memory.max') + if limit is None or not limit.isdigit(): + if limit == 'max': + return 9223372036854775807 + print("Warning: Can't detect memory limit from cgroups", + file=sys.stderr) + return None + return int(limit) + + +def get_number_of_cores(): + """ + Read number of CPU cores. + """ + + limits = [l for l in [_read_cpu_quota(), _read_cpuset_size(), _read_nproc()] if l] + if not limits: + return None + return min(limits) + + +def _read_cpu_quota(): + + quota, period = None, None + + quota = _read_file("/sys/fs/cgroup/cpu/cpu.cfs_quota_us") + if quota: + # cgroups v1 + quota = quota.strip() + if quota == "-1": + return None + + period = _read_file("/sys/fs/cgroup/cpu/cpu.cfs_period_us") + if period: + period = period.strip() + + else: + # cgroups v2 + line = _read_file("/sys/fs/cgroup/cpu.max") + if line: + fields = line.split() + + if len(fields) >= 2: + quota = fields[0] + if quota == "max": + return None + period = fields[1] + + + if quota and quota.isdigit() and period and period.isdigit(): + return int(quota)//int(period) + + print("Warning: Can't detect cpu quota from cgroups", + file=sys.stderr) + return None + + +def _read_cpuset_size(): + + core_count = 0 + + line = _read_file('/sys/fs/cgroup/cpuset/cpuset.cpus') + # If first file does not exist, try cgroups v2 file + line = line or _read_file('/sys/fs/cgroup/cpuset.cpus.effective') + if line is None: + # None of the files above exists when running podman as non-root, + # so in that case, this warning is printed every-time + print("Warning: Can't detect cpuset size from cgroups, will use nproc", + file=sys.stderr) + return None + + for group in line.split(','): + core_ids = list(map(int, group.split('-'))) + if len(core_ids) == 2: + core_count += core_ids[1] - core_ids[0] + 1 + else: + core_count += 1 + + return core_count + + +def _read_nproc(): + """ + Returns number of cores without looking at cgroup limits. + This might work as the last resort when running a container as non-root + where cgroups v2 resource limits cannot be set without a specific + configuration (per podman-run(1) man page). + """ + try: + stdout, stderr = subprocess.Popen('nproc', stdout=subprocess.PIPE).communicate() + return int(stdout) + except EnvironmentError as e: + if e.errno != errno.ENOENT: + raise + return None + + +if __name__ == "__main__": + env_vars = { + "MAX_MEMORY_LIMIT_IN_BYTES": 9223372036854775807, + "MEMORY_LIMIT_IN_BYTES": get_memory_limit(), + "NUMBER_OF_CORES": get_number_of_cores() + } + + env_vars = {k: v for k, v in env_vars.items() if v is not None} + + if env_vars.get("MEMORY_LIMIT_IN_BYTES", 0) >= 92233720368547: + env_vars["NO_MEMORY_LIMIT"] = "true" + + for key, value in env_vars.items(): + print("{0}={1}".format(key, value)) diff --git a/3.9-minimal/root/usr/bin/fix-permissions b/3.9-minimal/root/usr/bin/fix-permissions new file mode 100755 index 00000000..ddd33ace --- /dev/null +++ b/3.9-minimal/root/usr/bin/fix-permissions @@ -0,0 +1,27 @@ +#!/bin/sh + +# Allow this script to fail without failing a build +set +e + +SYMLINK_OPT=${2:--L} + +# Fix permissions on the given directory or file to allow group read/write of +# regular files and execute of directories. + +[ $(id -u) -ne 0 ] && CHECK_OWNER=" -uid $(id -u)" + +# If argument does not exist, script will still exit with 0, +# but at least we'll see something went wrong in the log +if ! [ -e "$1" ] ; then + echo "ERROR: File or directory $1 does not exist." >&2 + # We still want to end successfully + exit 0 +fi + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} \! -gid 0 -exec chgrp 0 {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} \! -perm -g+rw -exec chmod g+rw {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} -perm /u+x -a \! -perm /g+x -exec chmod g+x {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} -type d \! -perm /g+x -exec chmod g+x {} + + +# Always end successfully +exit 0 diff --git a/3.9-minimal/root/usr/bin/rpm-file-permissions b/3.9-minimal/root/usr/bin/rpm-file-permissions new file mode 100755 index 00000000..8be1fb0d --- /dev/null +++ b/3.9-minimal/root/usr/bin/rpm-file-permissions @@ -0,0 +1,21 @@ +#!/bin/sh + +CHECK_DIRS="/ /opt /etc /usr /usr/bin /usr/lib /usr/lib64 /usr/share /usr/libexec" + +rpm_format="[%{FILESTATES:fstate} %7{FILEMODES:octal} %{FILENAMES:shescape}\n]" + +rpm -q --qf "$rpm_format" filesystem | while read line +do + eval "set -- $line" + + case $1 in + normal) ;; + *) continue ;; + esac + + case " $CHECK_DIRS " in + *" $3 "*) + chmod "${2: -4}" "$3" + ;; + esac +done diff --git a/3.9-minimal/s2i/bin/assemble b/3.9-minimal/s2i/bin/assemble new file mode 100755 index 00000000..bbe66b8f --- /dev/null +++ b/3.9-minimal/s2i/bin/assemble @@ -0,0 +1,134 @@ +#!/bin/bash + +function is_django_installed() { + python -c "import django" &>/dev/null +} + +function should_collectstatic() { + is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] +} + +function virtualenv_bin() { + # New versions of Python (>3.6) should use venv module + # from stdlib instead of virtualenv package + python3.9 -m venv $1 +} + +# Install pipenv or micropipenv to the separate virtualenv to isolate it +# from system Python packages and packages in the main +# virtualenv. Executable is simlinked into ~/.local/bin +# to be accessible. This approach is inspired by pipsi +# (pip script installer). +function install_tool() { + echo "---> Installing $1 packaging tool ..." + VENV_DIR=$HOME/.local/venvs/$1 + virtualenv_bin "$VENV_DIR" + # First, try to install the tool without --isolated which means that if you + # have your own PyPI mirror, it will take it from there. If this try fails, try it + # again with --isolated which ignores external pip settings (env vars, config file) + # and installs the tool from PyPI (needs internet connetion). + # $1$2 combines package name with [extras] or version specifier if is defined as $2``` + if ! $VENV_DIR/bin/pip install -U $1$2; then + echo "WARNING: Installation of $1 failed, trying again from official PyPI with pip --isolated install" + $VENV_DIR/bin/pip install --isolated -U $1$2 # Combines package name with [extras] or version specifier if is defined as $2``` + fi + mkdir -p $HOME/.local/bin + ln -s $VENV_DIR/bin/$1 $HOME/.local/bin/$1 +} + +set -e + +# First of all, check that we don't have disallowed combination of ENVs +if [[ ! -z "$ENABLE_PIPENV" && ! -z "$ENABLE_MICROPIPENV" ]]; then + echo "ERROR: Pipenv and micropipenv cannot be enabled at the same time!" + # podman/buildah does not relay this exit code but it will be fixed hopefuly + # https://github.com/containers/buildah/issues/2305 + exit 3 +fi + +shopt -s dotglob +echo "---> Installing application source ..." +mv /tmp/src/* "$HOME" + +# set permissions for any installed artifacts +fix-permissions /opt/app-root -P + + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini +fi + +if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then + echo "---> Upgrading pip, setuptools and wheel to latest version ..." + if ! pip install -U pip setuptools wheel; then + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + pip install --isolated -U pip setuptools wheel + fi +fi + +if [[ ! -z "$ENABLE_PIPENV" ]]; then + if [[ ! -z "$PIN_PIPENV_VERSION" ]]; then + # Add == as a prefix to pipenv version, if defined + PIN_PIPENV_VERSION="==$PIN_PIPENV_VERSION" + fi + install_tool "pipenv" "$PIN_PIPENV_VERSION" + echo "---> Installing dependencies via pipenv ..." + if [[ -f Pipfile ]]; then + pipenv install --deploy + elif [[ -f requirements.txt ]]; then + pipenv install -r requirements.txt + fi + # pipenv check +elif [[ ! -z "$ENABLE_MICROPIPENV" ]]; then + install_tool "micropipenv" "[toml]" + echo "---> Installing dependencies via micropipenv ..." + # micropipenv detects Pipfile.lock and requirements.txt in this order + micropipenv install --deploy +elif [[ -f requirements.txt ]]; then + echo "---> Installing dependencies ..." + pip install -r requirements.txt +fi + +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." + pip install . +fi + +if should_collectstatic; then + ( + echo "---> Collecting Django static files ..." + + APP_HOME=$(readlink -f "${APP_HOME:-.}") + # Change the working directory to APP_HOME + PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" + cd "$APP_HOME" + + # Look for 'manage.py' in the current directory + manage_file=./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 -P diff --git a/3.9-minimal/s2i/bin/init-wrapper b/3.9-minimal/s2i/bin/init-wrapper new file mode 100755 index 00000000..26c8767d --- /dev/null +++ b/3.9-minimal/s2i/bin/init-wrapper @@ -0,0 +1,18 @@ +#!/bin/sh + +# Overview of how this script works: http://veithen.io/2014/11/16/sigterm-propagation.html +# Set a trap to kill the main app process when this +# init script receives SIGTERM or SIGINT +trap 'kill -s TERM $PID' TERM INT +# Execute the main application in the background +"$@" & +PID=$! +# wait command always terminates when trap is caught, even if the process hasn't finished yet +wait $PID +# Remove the trap and wait till the app process finishes completely +trap - TERM INT +# We wait again, since the first wait terminates when trap is caught +wait $PID +# Exit with the exit code of the app process +STATUS=$? +exit $STATUS diff --git a/3.9-minimal/s2i/bin/run b/3.9-minimal/s2i/bin/run new file mode 100755 index 00000000..bc0b3a15 --- /dev/null +++ b/3.9-minimal/s2i/bin/run @@ -0,0 +1,157 @@ +#!/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() { + # Using python and just python here because the script has + # platform-python in its shebang which we don't have in the minimal image + limit_vars=$(python /usr/bin/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)) + # According to http://docs.gunicorn.org/en/stable/design.html#how-many-workers, + # 12 workers should be enough to handle hundreds or thousands requests per second + default=$((default > 12 ? 12 : default)) + echo $default +} + +function maybe_run_in_init_wrapper() { + if [[ -z "$ENABLE_INIT_WRAPPER" ]]; then + exec "$@" + else + exec $STI_SCRIPTS_PATH/init-wrapper "$@" + fi +} + +# Look for gunicorn>=20.1.0 to utilize gunicorn.conf.py +if is_gunicorn_installed && [[ -f "gunicorn.conf.py" ]]; then + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + +APP_HOME=$(readlink -f "${APP_HOME:-.}") +# Change the working directory to APP_HOME +PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" +cd "$APP_HOME" + +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 + fi +fi + +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi +fi + +# Look for 'manage.py' in the current directory +manage_file=./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 not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + +if is_gunicorn_installed; then + setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) + # Look for wsgi module in the current directory + if [[ -z "$APP_MODULE" && -f "./wsgi.py" ]]; then + APP_MODULE=wsgi + elif [[ -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)} + + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" + fi +fi + +if is_django_installed; then + if [[ -f "$manage_file" ]]; then + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." + echo "WARNING: this is NOT a recommended way to run you application in production!" + echo "Consider using gunicorn or some other production web server." + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT + 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/3.9-minimal/s2i/bin/usage b/3.9-minimal/s2i/bin/usage new file mode 100755 index 00000000..9df53ec5 --- /dev/null +++ b/3.9-minimal/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 < -- curl 127.0.0.1:8080 +EOF diff --git a/3.9-minimal/test/__init__.py b/3.9-minimal/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.9-minimal/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/3.9-minimal/test/app-home-test-app b/3.9-minimal/test/app-home-test-app new file mode 120000 index 00000000..f6b42e77 --- /dev/null +++ b/3.9-minimal/test/app-home-test-app @@ -0,0 +1 @@ +../../examples/app-home-test-app \ No newline at end of file diff --git a/3.9-minimal/test/app-module-test-app b/3.9-minimal/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.9-minimal/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/3.9-minimal/test/check_imagestreams.py b/3.9-minimal/test/check_imagestreams.py new file mode 120000 index 00000000..56bb2be7 --- /dev/null +++ b/3.9-minimal/test/check_imagestreams.py @@ -0,0 +1 @@ +../../common/check_imagestreams.py \ No newline at end of file diff --git a/3.9-minimal/test/conftest.py b/3.9-minimal/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.9-minimal/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.9-minimal/test/django-different-port-test-app b/3.9-minimal/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.9-minimal/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/3.9-minimal/test/django-test-app b/3.9-minimal/test/django-test-app new file mode 120000 index 00000000..52cebf3b --- /dev/null +++ b/3.9-minimal/test/django-test-app @@ -0,0 +1 @@ +../../examples/django-test-app \ No newline at end of file diff --git a/3.9-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl b/3.9-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl new file mode 120000 index 00000000..f5c5b907 --- /dev/null +++ b/3.9-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/mod_wsgi.Dockerfile.tpl \ No newline at end of file diff --git a/3.9-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl b/3.9-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl new file mode 100644 index 00000000..69defb98 --- /dev/null +++ b/3.9-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl @@ -0,0 +1,20 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +ENV UPGRADE_PIP_TO_LATEST=1 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +# Install packages necessary for compiling uwsgi from source +# pkgconfig(python-3.9) is provided by both python3-devel in c9s +# and python39-devel in UBI8. +RUN microdnf install -y gcc "pkgconfig(python-3.9)" which +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run diff --git a/3.9-minimal/test/gunicorn-config-different-port-test-app b/3.9-minimal/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.9-minimal/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.9-minimal/test/gunicorn-different-port-test-app b/3.9-minimal/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.9-minimal/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.9-minimal/test/gunicorn-python-configfile-different-port-test-app b/3.9-minimal/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.9-minimal/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/3.9-minimal/test/imagestreams b/3.9-minimal/test/imagestreams new file mode 120000 index 00000000..7a0aee9c --- /dev/null +++ b/3.9-minimal/test/imagestreams @@ -0,0 +1 @@ +../../imagestreams \ No newline at end of file diff --git a/3.9-minimal/test/locale-test-app b/3.9-minimal/test/locale-test-app new file mode 120000 index 00000000..9105ec08 --- /dev/null +++ b/3.9-minimal/test/locale-test-app @@ -0,0 +1 @@ +../../examples/locale-test-app \ No newline at end of file diff --git a/3.9-minimal/test/micropipenv-requirements-test-app b/3.9-minimal/test/micropipenv-requirements-test-app new file mode 120000 index 00000000..cb2ddcf0 --- /dev/null +++ b/3.9-minimal/test/micropipenv-requirements-test-app @@ -0,0 +1 @@ +../../examples/micropipenv-requirements-test-app \ No newline at end of file diff --git a/3.9-minimal/test/micropipenv-test-app/.gitignore b/3.9-minimal/test/micropipenv-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/3.9-minimal/test/micropipenv-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/3.9-minimal/test/micropipenv-test-app/.s2i/environment b/3.9-minimal/test/micropipenv-test-app/.s2i/environment new file mode 100644 index 00000000..29efed4a --- /dev/null +++ b/3.9-minimal/test/micropipenv-test-app/.s2i/environment @@ -0,0 +1,5 @@ +ENABLE_MICROPIPENV=true +DISABLE_SETUP_PY_PROCESSING=true +# This tests second try to install micropipenv with --isolated +# because the first one won't work with following setting +PIP_INDEX_URL=https://example.com/ diff --git a/2.7/test/micropipenv-test-app/Pipfile b/3.9-minimal/test/micropipenv-test-app/Pipfile similarity index 89% rename from 2.7/test/micropipenv-test-app/Pipfile rename to 3.9-minimal/test/micropipenv-test-app/Pipfile index 5f1a38e9..117b223d 100644 --- a/2.7/test/micropipenv-test-app/Pipfile +++ b/3.9-minimal/test/micropipenv-test-app/Pipfile @@ -11,4 +11,4 @@ requests = "==2.20.0" pytest = ">=2.8.0" [requires] -python_version = "2.7" +python_version = "3.9" diff --git a/3.9-minimal/test/micropipenv-test-app/Pipfile.lock b/3.9-minimal/test/micropipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..25741732 --- /dev/null +++ b/3.9-minimal/test/micropipenv-test-app/Pipfile.lock @@ -0,0 +1,145 @@ +{ + "_meta": { + "hash": { + "sha256": "9d47e9d2d35583e081cf783e9421e75fafb102d07969ad6f74546f48db3a8101" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", + "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + ], + "version": "==2020.6.20" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "e1839a8": { + "editable": true, + "path": "." + }, + "gunicorn": { + "hashes": [ + "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", + "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" + ], + "markers": "python_version >= '3.4'", + "version": "==20.0.4" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "requests": { + "hashes": [ + "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", + "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" + ], + "index": "pypi", + "version": "==2.20.0" + }, + "testapp": { + "editable": true, + "path": "." + }, + "urllib3": { + "hashes": [ + "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", + "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'", + "version": "==1.24.3" + } + }, + "develop": { + "attrs": { + "hashes": [ + "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", + "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.2.0" + }, + "iniconfig": { + "hashes": [ + "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437", + "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69" + ], + "version": "==1.0.1" + }, + "packaging": { + "hashes": [ + "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", + "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.4" + }, + "pluggy": { + "hashes": [ + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.1" + }, + "py": { + "hashes": [ + "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", + "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.9.0" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.7" + }, + "pytest": { + "hashes": [ + "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9", + "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92" + ], + "index": "pypi", + "version": "==6.1.1" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.15.0" + }, + "toml": { + "hashes": [ + "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", + "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + ], + "version": "==0.10.1" + } + } +} diff --git a/3.9-minimal/test/micropipenv-test-app/setup.py b/3.9-minimal/test/micropipenv-test-app/setup.py new file mode 100644 index 00000000..e3134094 --- /dev/null +++ b/3.9-minimal/test/micropipenv-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/3.9-minimal/test/micropipenv-test-app/testapp.py b/3.9-minimal/test/micropipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.9-minimal/test/micropipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.9-minimal/test/numpy-test-app b/3.9-minimal/test/numpy-test-app new file mode 120000 index 00000000..b064f87f --- /dev/null +++ b/3.9-minimal/test/numpy-test-app @@ -0,0 +1 @@ +../../examples/numpy-test-app \ No newline at end of file diff --git a/3.9-minimal/test/pipenv-and-micropipenv-should-fail-test-app b/3.9-minimal/test/pipenv-and-micropipenv-should-fail-test-app new file mode 120000 index 00000000..cf830645 --- /dev/null +++ b/3.9-minimal/test/pipenv-and-micropipenv-should-fail-test-app @@ -0,0 +1 @@ +../../src/test/pipenv-and-micropipenv-should-fail-test-app \ No newline at end of file diff --git a/3.9-minimal/test/pipenv-test-app/.gitignore b/3.9-minimal/test/pipenv-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/3.9-minimal/test/pipenv-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/3.9-minimal/test/pipenv-test-app/.s2i/environment b/3.9-minimal/test/pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..4a4f7605 --- /dev/null +++ b/3.9-minimal/test/pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=true +DISABLE_SETUP_PY_PROCESSING=true diff --git a/3.7/test/micropipenv-test-app/Pipfile b/3.9-minimal/test/pipenv-test-app/Pipfile similarity index 89% rename from 3.7/test/micropipenv-test-app/Pipfile rename to 3.9-minimal/test/pipenv-test-app/Pipfile index 2ab0cfa5..117b223d 100644 --- a/3.7/test/micropipenv-test-app/Pipfile +++ b/3.9-minimal/test/pipenv-test-app/Pipfile @@ -11,4 +11,4 @@ requests = "==2.20.0" pytest = ">=2.8.0" [requires] -python_version = "3.7" +python_version = "3.9" diff --git a/3.9-minimal/test/pipenv-test-app/Pipfile.lock b/3.9-minimal/test/pipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..25741732 --- /dev/null +++ b/3.9-minimal/test/pipenv-test-app/Pipfile.lock @@ -0,0 +1,145 @@ +{ + "_meta": { + "hash": { + "sha256": "9d47e9d2d35583e081cf783e9421e75fafb102d07969ad6f74546f48db3a8101" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", + "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + ], + "version": "==2020.6.20" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "e1839a8": { + "editable": true, + "path": "." + }, + "gunicorn": { + "hashes": [ + "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", + "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" + ], + "markers": "python_version >= '3.4'", + "version": "==20.0.4" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "requests": { + "hashes": [ + "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", + "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" + ], + "index": "pypi", + "version": "==2.20.0" + }, + "testapp": { + "editable": true, + "path": "." + }, + "urllib3": { + "hashes": [ + "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", + "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'", + "version": "==1.24.3" + } + }, + "develop": { + "attrs": { + "hashes": [ + "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", + "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.2.0" + }, + "iniconfig": { + "hashes": [ + "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437", + "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69" + ], + "version": "==1.0.1" + }, + "packaging": { + "hashes": [ + "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", + "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.4" + }, + "pluggy": { + "hashes": [ + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.1" + }, + "py": { + "hashes": [ + "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", + "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.9.0" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.7" + }, + "pytest": { + "hashes": [ + "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9", + "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92" + ], + "index": "pypi", + "version": "==6.1.1" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.15.0" + }, + "toml": { + "hashes": [ + "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", + "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + ], + "version": "==0.10.1" + } + } +} diff --git a/3.9-minimal/test/pipenv-test-app/setup.py b/3.9-minimal/test/pipenv-test-app/setup.py new file mode 100644 index 00000000..e3134094 --- /dev/null +++ b/3.9-minimal/test/pipenv-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/3.9-minimal/test/pipenv-test-app/testapp.py b/3.9-minimal/test/pipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.9-minimal/test/pipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.9-minimal/test/poetry-src-layout-test-app b/3.9-minimal/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.9-minimal/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.9-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.9-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..9bfe6709 --- /dev/null +++ b/3.9-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2021.5.29 diff --git a/3.9-minimal/test/pyuwsgi-pipenv-test-app/app.sh b/3.9-minimal/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.9-minimal/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.9-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.9-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.9-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.9-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py b/3.9-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.9-minimal/test/pyuwsgi-pipenv-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() diff --git a/3.9-minimal/test/run b/3.9-minimal/test/run new file mode 100755 index 00000000..8de24fe3 --- /dev/null +++ b/3.9-minimal/test/run @@ -0,0 +1,308 @@ +#!/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. +# +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,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${MINIMAL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) + +# TODO: Make command compatible for Mac users +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile_minimal +" + +if [[ -z $VERSION ]]; then + echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 + exit 1 +fi + +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} + +. test/test-lib.sh + +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 ..." + ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + return 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() { + 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 +} +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + info "Waiting for application container to start $CONTAINER_ARGS ..." + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && return 0 + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return 1 +} + +test_s2i_usage() { + info "Testing 's2i usage' ..." + ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage ..." + docker run --rm ${IMAGE_NAME} &>/dev/null +} + +test_scl_usage() { + local run_cmd="$1" + local expected="$2" + local cid_file="$3" + + info "Testing command availability in the image" + 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="$CID_FILE_DIR"/"$(mktemp -u -p . --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 + # Some test apps have tests in their startup code so we have to check + # that the container starts at all + ct_check_testcase_result $? + + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? + test_connection + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") +} + +test_from_dockerfile(){ + info "Test from Dockerfile" + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? +} + +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" + + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. + + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? + + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? + else + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." + fi + +} + +test_application_with_user() { + # test application with random user + CONTAINER_ARGS="--user 12345" test_application + +} + +test_application_enable_init_wrapper() { + # test application with init wrapper + CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue + + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" + + cleanup ${app} +done + +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.9-minimal/test/run-openshift-pytest b/3.9-minimal/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.9-minimal/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.9-minimal/test/run-pytest b/3.9-minimal/test/run-pytest new file mode 100755 index 00000000..9810fe89 --- /dev/null +++ b/3.9-minimal/test/run-pytest @@ -0,0 +1,16 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +if python3 -c 'import sys; sys.exit(0 if sys.version_info < (3,13) else 1)'; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.9-minimal/test/setup-cfg-test-app b/3.9-minimal/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.9-minimal/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/3.9-minimal/test/setup-requirements-test-app b/3.9-minimal/test/setup-requirements-test-app new file mode 120000 index 00000000..c909ccb9 --- /dev/null +++ b/3.9-minimal/test/setup-requirements-test-app @@ -0,0 +1 @@ +../../examples/setup-requirements-test-app \ No newline at end of file diff --git a/3.9-minimal/test/setup-test-app b/3.9-minimal/test/setup-test-app new file mode 120000 index 00000000..1b49f26f --- /dev/null +++ b/3.9-minimal/test/setup-test-app @@ -0,0 +1 @@ +../../examples/setup-test-app \ No newline at end of file diff --git a/3.9-minimal/test/standalone-custom-pypi-index-test-app b/3.9-minimal/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.9-minimal/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/3.9-minimal/test/standalone-test-app b/3.9-minimal/test/standalone-test-app new file mode 120000 index 00000000..3f158948 --- /dev/null +++ b/3.9-minimal/test/standalone-test-app @@ -0,0 +1 @@ +../../examples/standalone-test-app \ No newline at end of file diff --git a/3.9-minimal/test/test-lib.sh b/3.9-minimal/test/test-lib.sh new file mode 120000 index 00000000..1ac99b93 --- /dev/null +++ b/3.9-minimal/test/test-lib.sh @@ -0,0 +1 @@ +../../common/test-lib.sh \ No newline at end of file diff --git a/3.9-minimal/test/test_container_application.py b/3.9-minimal/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.9-minimal/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.9-minimal/test/test_container_basics.py b/3.9-minimal/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.9-minimal/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.9-minimal/test/test_ocp_deploy_templates.py b/3.9-minimal/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.9-minimal/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.9-minimal/test/test_ocp_helm_python_django_app.py b/3.9-minimal/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.9-minimal/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.9-minimal/test/test_ocp_helm_python_django_psql_persistent.py b/3.9-minimal/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.9-minimal/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.9-minimal/test/test_ocp_helm_python_imagestreams.py b/3.9-minimal/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.9-minimal/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.9-minimal/test/test_ocp_imagestreams_quickstart.py b/3.9-minimal/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.9-minimal/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.9-minimal/test/test_ocp_python_ex_standalone.py b/3.9-minimal/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.9-minimal/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.9-minimal/test/test_ocp_python_ex_template.py b/3.9-minimal/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.9-minimal/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file diff --git a/3.9-minimal/test/uwsgi-test-app b/3.9-minimal/test/uwsgi-test-app new file mode 120000 index 00000000..c3ccf241 --- /dev/null +++ b/3.9-minimal/test/uwsgi-test-app @@ -0,0 +1 @@ +../../examples/uwsgi-test-app \ No newline at end of file diff --git a/3.9/Dockerfile.fedora b/3.9/Dockerfile.c9s similarity index 55% rename from 3.9/Dockerfile.fedora rename to 3.9/Dockerfile.c9s index 9bc2011c..c3475d9f 100644 --- a/3.9/Dockerfile.fedora +++ b/3.9/Dockerfile.c9s @@ -1,6 +1,6 @@ # This image provides a Python 3.9 environment you can use to run your Python # applications. -FROM registry.fedoraproject.org/f33/s2i-base:latest +FROM quay.io/sclorg/s2i-base-c9s:c9s EXPOSE 8080 @@ -12,9 +12,7 @@ ENV PYTHON_VERSION=3.9 \ LANG=en_US.UTF-8 \ PIP_NO_CACHE_DIR=off - ENV NAME=python3 \ - VERSION=0 \ ARCH=x86_64 ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ @@ -33,24 +31,30 @@ LABEL summary="$SUMMARY" \ io.openshift.expose-services="8080:http" \ io.openshift.tags="builder,python,python39,python-39,rh-python39" \ com.redhat.component="$NAME" \ - name="$FGC/$NAME" \ - version="$VERSION" \ + name="sclorg/python-39-c9s" \ usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.9/test/setup-test-app/ $FGC/$NAME python-sample-app" \ maintainer="SoftwareCollections.org " -RUN INSTALL_PKGS="python3 python3-devel python3-setuptools python3-pip nss_wrapper \ - httpd httpd-devel atlas-devel gcc-gfortran libffi-devel \ - libtool-ltdl enchant redhat-rpm-config" && \ - dnf -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ +RUN INSTALL_PKGS="python3 python3-devel python3-setuptools python3-pip nss_wrapper-libs \ + httpd httpd-devel mod_ssl mod_auth_gssapi mod_ldap \ + mod_session atlas-devel gcc-gfortran libffi-devel libtool-ltdl \ + enchant krb5-devel" && \ + yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ - dnf -y clean all --enablerepo='*' + yum -y clean all --enablerepo='*' # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. -COPY ./s2i/bin/ $STI_SCRIPTS_PATH +COPY 3.9/s2i/bin/ $STI_SCRIPTS_PATH # Copy extra files to the image. -COPY ./root/ / +COPY 3.9/root/ / +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.9/root/opt/wheels /opt/wheels # - Create a Python virtual environment for use by any application to avoid # potential conflicts with Python packages preinstalled in the main Python # installation. @@ -58,11 +62,23 @@ COPY ./root/ / # writable as OpenShift default security model is to run the container # under random UID. RUN python3.9 -m venv ${APP_ROOT} && \ +# Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ +# We have to upgrade pip to a newer verison because \ +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ +# support platforms like ppc64le, aarch64 or armv7 \ +# We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ +# because it's tested better then whatever version from PyPI and contains useful patches. \ +# We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ +# to reinstall itself in the next build phases in the assemble script if user wants the latest version \ +${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +rm -r /opt/wheels && \ chown -R 1001:0 ${APP_ROOT} && \ -fix-permissions ${APP_ROOT} -P - -# For Fedora scl_enable isn't sourced automatically in s2i-core -# so virtualenv needs to be activated this way +fix-permissions ${APP_ROOT} -P && \ +# The following echo adds the unset command for the variables set below to the \ +# venv activation script. This prevents the virtual environment from being \ +# activated multiple times and also every time the prompt is rendered. \ +echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells ENV BASH_ENV="${APP_ROOT}/bin/activate" \ ENV="${APP_ROOT}/bin/activate" \ PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" diff --git a/3.9/Dockerfile.rhel9 b/3.9/Dockerfile.rhel9 new file mode 100644 index 00000000..9fd315d4 --- /dev/null +++ b/3.9/Dockerfile.rhel9 @@ -0,0 +1,95 @@ +# This image provides a Python 3.9 environment you can use to run your Python +# applications. +FROM ubi9/s2i-base:1 + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.9 \ + PATH=$HOME/.local/bin/:$PATH \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi9-python-39 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off + +ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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.9" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python39,python-39,rh-python39" \ + com.redhat.component="python-39-container" \ + name="ubi9/python-39" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.9/test/setup-test-app/ ubi9/python-39 python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi9-python-39" \ + maintainer="SoftwareCollections.org " + +RUN INSTALL_PKGS="python3 python3-devel python3-setuptools python3-pip nss_wrapper-libs \ + httpd httpd-devel mod_ssl mod_auth_gssapi mod_ldap \ + mod_session atlas-devel gcc-gfortran libffi-devel libtool-ltdl \ + enchant krb5-devel" && \ + yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + # Remove redhat-logos-httpd (httpd dependency) to keep image size smaller. + rpm -e --nodeps redhat-logos-httpd && \ + yum -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.9/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.9/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.9/root/opt/wheels /opt/wheels +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python3.9 -m venv ${APP_ROOT} && \ + # Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ + # We have to upgrade pip to a newer verison because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions && \ + # The following echo adds the unset command for the variables set below to the \ + # venv activation script. This prevents the virtual environment from being \ + # activated multiple times and also every time the prompt is rendered. \ + echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.9/README.md b/3.9/README.md index 13e95647..e952d650 100644 --- a/3.9/README.md +++ b/3.9/README.md @@ -1,11 +1,11 @@ Python 3.9 container image -=================== +========================= This container image includes Python 3.9 as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.9 applications. -Users can choose between RHEL and CentOS based builder images. -The RHEL images are available in the [Red Hat Container Catalog](https://access.redhat.com/containers/), -the CentOS images are available on [Docker Hub](https://hub.docker.com/r/centos/), -and the Fedora images are available in [Fedora Registry](https://registry.fedoraproject.org/). +Users can choose between RHEL, CentOS Stream, and Fedora based builder images. +The RHEL images are available in the [Red Hat Container Catalog](https://catalog.redhat.com/software/containers/explore), +CentOS Stream images are available on [Quay.io](https://quay.io/organization/sclorg), +and the Fedora images are available on [Quay.io](https://quay.io/organization/fedora). The resulting image can be run using [podman](https://github.com/containers/libpod) or [docker](http://docker.io). @@ -29,28 +29,132 @@ modules for their web applications. There is no guarantee for any specific npm o version, that is included in the image; those versions can be changed anytime and the nodejs itself is included just to make the npm work. -Usage ---------------------- +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Usage in Openshift +------------------ -For this, we will assume that you are using the supported image, available via `python:3.9` imagestream tag in Openshift. -Building a simple [python-sample-app](https://github.com/sclorg/s2i-python-container/tree/master/3.9/test/setup-test-app) application +For this, we will assume that you are using one of the supported images available via imagestream tags in Openshift, eg. `python:3.9-ubi9` +Building a simple [python-sample-app](https://github.com/sclorg/django-ex.git) application in Openshift can be achieved with the following step: ``` - oc new-app python:3.9~https://github.com/sclorg/s2i-python-container.git --context-dir=3.9/test/setup-test-app/ + oc new-app python:3.9-ubi9~https://github.com/sclorg/django-ex.git ``` -The same application can also be built using the standalone [S2I](https://github.com/openshift/source-to-image) application on systems that have it available: +**Accessing the application:** +``` +$ oc get pods +$ oc exec -- curl 127.0.0.1:8080 +``` - ``` - $ s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.9/test/setup-test-app/ python-sample-app - ``` +Source-to-Image framework and scripts +------------------------------------- +This image supports the [Source-to-Image](https://docs.openshift.com/container-platform/4.14/openshift_images/create-images.html#images-create-s2i_create-images) +(S2I) strategy in OpenShift. The Source-to-Image is an OpenShift framework +which makes it easy to write images that take application source code as +an input, use a builder image like this Python container image, and produce +a new image that runs the assembled application as an output. + +To support the Source-to-Image framework, important scripts are included in the builder image: + +* The `/usr/libexec/s2i/assemble` script inside the image is run to produce a new image with the application artifacts. +The script takes sources of a given application and places them into appropriate directories inside the image. +It utilizes some common patterns in Perl application development (see the **Environment variables** section below). +* The `/usr/libexec/s2i/run` script is set as the default command in the resulting container image (the new image with the application artifacts). +It runs your application according to settings in `APP_MODULE`, `APP_FILE` or `APP_SCRIPT` environment variables or it tries to detect the best +way automatically. + +Building an application using a Dockerfile +------------------------------------------ +Compared to the Source-to-Image strategy, using a Dockerfile is a more +flexible way to build a Python container image with an application. +Use a Dockerfile when Source-to-Image is not sufficiently flexible for you or +when you build the image outside of the OpenShift environment. + +To use the Python image in a Dockerfile, follow these steps: +#### 1. Pull a base builder image to build on -Where `` is the s2i-python image you [downloaded from RHEL, Centos or Fedora registry](../README.md#Download) or [built](../README.md#Build) from these sources. For example ubi8/python-36, centos/python-36-centos7 or f31/python3. +``` +podman pull registry.access.redhat.com/ubi9/python-39 +``` -**Accessing the application:** +#### 2. Pull and application code + +An example application available at https://github.com/sclorg/django-ex.git is used here. Feel free to clone the repository for further experiments. +You can also take a look at code examples in s2i-python-container repository: https://github.com/sclorg/s2i-python-container/tree/master/examples + +``` +git clone https://github.com/sclorg/django-ex.git app-src ``` -$ curl 127.0.0.1:8080 + +#### 3. Prepare an application inside a container + +This step usually consists of at least these parts: + +* putting the application source into the container +* installing the dependencies +* setting the default command in the resulting image + +For all these three parts, users can either setup all manually and use commands `python` and `pip` explicitly in the Dockerfile, +or users can use the Source-to-Image scripts inside the image. + +The manual way comes with the highest level of flexibility but requires you to know how to work +with modules or software collections manually, how to setup virtual environment with the right version +of Python and many more. On the other hand, using Source-to-Image scripts makes your Dockerfile +prepared for a future flawless switch to a newer or different platform. + +To use the Source-to-Image scripts and build an image using a Dockerfile, create a Dockerfile with this content: + +``` +FROM registry.access.redhat.com/ubi9/python-39 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you decide not to use the Source-to-Image scripts, you will need to manually tailor the Dockerfile to your application and its needs. +Example Dockerfile for a simple Django application: + +``` +FROM registry.access.redhat.com/ubi9/python-39 + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 +``` + +#### 4. Build a new image from a Dockerfile prepared in the previous step + +``` +podman build -t python-app . +``` + +#### 5. Run the resulting image with final application + +``` +podman run -d python-app ``` Environment variables @@ -115,9 +219,9 @@ file inside your source code repository. 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_SETUP_PY_PROCESSING** +* **DISABLE_SETUP_PY_PROCESSING** / **DISABLE_PYPROJECT_TOML_PROCESSING** - Set this to a non-empty value to skip processing of setup.py script if you + Set this to a non-empty value to skip processing of setup.{py,cfg} or pyproject.toml script if you use `-e .` in requirements.txt to trigger its processing or you don't want your application to be installed into site-packages directory. @@ -127,6 +231,7 @@ file inside your source code repository. the higher-level Python packaging tool, to manage dependencies of the application. This should be used only if your project contains properly formated Pipfile and Pipfile.lock. + * **PIN_PIPENV_VERSION** Set this variable together with `ENABLE_PIPENV` to use a specific version of Pipenv. @@ -158,12 +263,18 @@ file inside your source code repository. the custom index, the container will try to install/update them from upstream PyPI afterwards. +* **PORT** + + HTTP(S) port your application should listen on. The default is 8080. + `PORT` is used only for Django development server and for Gunicorn + with the default configutation (no `APP_CONFIG` or `GUNICORN_CMD_ARGS` specified). + * **UPGRADE_PIP_TO_LATEST** Set this variable to a non-empty value to have the 'pip' program and related python packages (setuptools and wheel) 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. + before any Python packages are installed. If not set, the container will use + the stable pip version this container was built with, taken from a recent Fedora release. * **WEB_CONCURRENCY** @@ -196,7 +307,7 @@ However, if these files exist they will affect the behavior of the build process Configures various aspects of the project, including installation of dependencies, as documented - [here](https://packaging.python.org/en/latest/distributing.html#setup-py). + [here](https://packaging.python.org/guides/distributing-packages-using-setuptools/?highlight=distributing#setup-py). For most projects, it is sufficient to simply use `requirements.txt` or `Pipfile`. Set `DISABLE_SETUP_PY_PROCESSING` environment variable to true in order to skip processing of this file. @@ -221,6 +332,9 @@ following ways, in precedence order: If you have both Django and Gunicorn in your requirements, your Django project will automatically be served using Gunicorn. + The default setting for Gunicorn (`--bind=0.0.0.0:$PORT --access-logfile=-`) is applied + only if both `$APP_CONFIG` and `$GUNICORN_CMD_ARGS` are not defined. + * **Django development server** If you have Django in your requirements but don't have Gunicorn, then your @@ -244,7 +358,7 @@ following ways, in precedence order: application. Hot deploy ---------------------- +---------- If you are using Django, hot deploy will work out of the box. @@ -255,7 +369,7 @@ 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 podman's (or docker's) -[exec](https://github.com/containers/libpod/blob/master/docs/podman-exec.1.md) command: +[exec](https://github.com/containers/podman/blob/main/docs/source/markdown/podman-exec.1.md) command: ``` podman exec -it /bin/bash @@ -269,5 +383,6 @@ See also -------- Dockerfile and other sources are available on https://github.com/sclorg/s2i-python-container. In that repository you also can find another versions of Python environment Dockerfiles. -Dockerfile for CentOS is called `Dockerfile`, Dockerfile for RHEL7 is called `Dockerfile.rhel7`, -for RHEL8 it's `Dockerfile.rhel8` and the Fedora Dockerfile is called `Dockerfile.fedora`. +Dockerfile for RHEL8 is called `Dockerfile.rhel8`, for RHEL9 it's `Dockerfile.rhel9`, +for CentOS Stream 9 it's `Dockerfile.c9s`, for CentOS Stream 10 it's `Dockerfile.c10s`, +and the Fedora Dockerfile is called `Dockerfile.fedora`. diff --git a/3.9/cccp.yml b/3.9/cccp.yml deleted file mode 100644 index db226f5c..00000000 --- a/3.9/cccp.yml +++ /dev/null @@ -1,3 +0,0 @@ -# This is for the purpose of building this container -# on the centos container pipeline. -job-id: python-39-centos7 diff --git a/3.9/root/opt/app-root/etc/generate_container_user b/3.9/root/opt/app-root/etc/generate_container_user index 43ee7ecb..a7fd74d6 100644 --- a/3.9/root/opt/app-root/etc/generate_container_user +++ b/3.9/root/opt/app-root/etc/generate_container_user @@ -9,7 +9,7 @@ if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"1001" ]; then 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 + echo "default:x:${USER_ID}:${GROUP_ID}:Default Application User:${HOME}:/bin/bash" >> $NSS_WRAPPER_PASSWD export NSS_WRAPPER_PASSWD export NSS_WRAPPER_GROUP diff --git a/3.9/root/opt/app-root/etc/scl_enable b/3.9/root/opt/app-root/etc/scl_enable deleted file mode 100644 index 766e4de6..00000000 --- a/3.9/root/opt/app-root/etc/scl_enable +++ /dev/null @@ -1,9 +0,0 @@ -# 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 -if head "/etc/redhat-release" | grep -q "^CentOS Linux release 7" || \ - head "/etc/redhat-release" | grep -q "^Red Hat Enterprise Linux\( Server\)\? release 7"; then - source scl_source enable httpd24 $NODEJS_SCL rh-python39 -fi -source /opt/app-root/bin/activate diff --git a/3.9/root/opt/wheels b/3.9/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.9/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.9/s2i/bin/assemble b/3.9/s2i/bin/assemble index 10996bec..bbe66b8f 100755 --- a/3.9/s2i/bin/assemble +++ b/3.9/s2i/bin/assemble @@ -53,20 +53,17 @@ mv /tmp/src/* "$HOME" # set permissions for any installed artifacts fix-permissions /opt/app-root -P -# We have to first upgrade pip to at least 19.3 because: -# * pip < 9 does not support different packages' versions for Python 2/3 -# * pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 wheels -# support platforms like ppc64le, aarch64 or armv7 -echo "---> Upgrading pip to version 19.3.1 ..." -if ! pip install -U "pip==19.3.1"; then - echo "WARNING: Installation of 'pip==19.3.1' failed, trying again from official PyPI with pip --isolated install" - pip install --isolated -U "pip==19.3.1" + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini fi if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then - echo "---> Upgrading pip to latest version ..." + echo "---> Upgrading pip, setuptools and wheel to latest version ..." if ! pip install -U pip setuptools wheel; then - echo "WARNING: Installation of the latest pip,setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" pip install --isolated -U pip setuptools wheel fi fi @@ -94,8 +91,13 @@ elif [[ -f requirements.txt ]]; then pip install -r requirements.txt fi -if [[ -f setup.py && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then - echo "---> Installing application ..." +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." pip install . fi diff --git a/3.9/s2i/bin/run b/3.9/s2i/bin/run index ee1e1d3b..7003f038 100755 --- a/3.9/s2i/bin/run +++ b/3.9/s2i/bin/run @@ -43,30 +43,54 @@ 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 + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + APP_HOME=$(readlink -f "${APP_HOME:-.}") # Change the working directory to APP_HOME PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" cd "$APP_HOME" -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" +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 fi - maybe_run_in_init_wrapper "$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) ..." - maybe_run_in_init_wrapper python "$APP_FILE" -else - test -n "$app_file_check" && (>&2 echo "ERROR: file '$app_file_check' not found.") && exit 1 +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi fi # Look for 'manage.py' in the current directory @@ -82,6 +106,11 @@ if should_migrate; then fi fi +# If not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + if is_gunicorn_installed; then setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) # Look for wsgi module in the current directory @@ -94,17 +123,27 @@ if is_gunicorn_installed; then 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" + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" fi fi if is_django_installed; then if [[ -f "$manage_file" ]]; then - echo "---> Serving application with 'manage.py runserver' ..." + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." echo "WARNING: this is NOT a recommended way to run you application in production!" echo "Consider using gunicorn or some other production web server." - maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:8080 + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT else echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." echo "Skipped 'python manage.py runserver'." diff --git a/3.9/s2i/bin/usage b/3.9/s2i/bin/usage index a4336155..a45027f0 100755 --- a/3.9/s2i/bin/usage +++ b/3.9/s2i/bin/usage @@ -6,13 +6,13 @@ NAMESPACE=centos cat < -- curl 127.0.0.1:8080 EOF diff --git a/3.9/test/__init__.py b/3.9/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.9/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/3.9/test/app-module-test-app b/3.9/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.9/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/3.9/test/conftest.py b/3.9/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.9/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.9/test/django-different-port-test-app b/3.9/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.9/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/3.9/test/from-dockerfile/Dockerfile.tpl b/3.9/test/from-dockerfile/Dockerfile.tpl new file mode 120000 index 00000000..ba88ccc4 --- /dev/null +++ b/3.9/test/from-dockerfile/Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/Dockerfile.tpl \ No newline at end of file diff --git a/3.9/test/from-dockerfile/Dockerfile_no_s2i.tpl b/3.9/test/from-dockerfile/Dockerfile_no_s2i.tpl new file mode 100644 index 00000000..da2f43ff --- /dev/null +++ b/3.9/test/from-dockerfile/Dockerfile_no_s2i.tpl @@ -0,0 +1,16 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 diff --git a/3.9/test/gunicorn-config-different-port-test-app b/3.9/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.9/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.9/test/gunicorn-different-port-test-app b/3.9/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.9/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.9/test/gunicorn-python-configfile-different-port-test-app b/3.9/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.9/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/3.9/test/micropipenv-test-app/testapp.py b/3.9/test/micropipenv-test-app/testapp.py index a9193eec..fe200352 100644 --- a/3.9/test/micropipenv-test-app/testapp.py +++ b/3.9/test/micropipenv-test-app/testapp.py @@ -1,7 +1,12 @@ +import sys import requests def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) - assert requests.__version__ == '2.20.0' + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' return [b"Hello from gunicorn WSGI application!"] diff --git a/3.9/test/pin-pipenv-version-test-app b/3.9/test/pin-pipenv-version-test-app deleted file mode 120000 index ef18267b..00000000 --- a/3.9/test/pin-pipenv-version-test-app +++ /dev/null @@ -1 +0,0 @@ -../../examples/pin-pipenv-version-test-app \ No newline at end of file diff --git a/3.9/test/pin-pipenv-version-test-app/.s2i/environment b/3.9/test/pin-pipenv-version-test-app/.s2i/environment new file mode 100644 index 00000000..9bfe6709 --- /dev/null +++ b/3.9/test/pin-pipenv-version-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2021.5.29 diff --git a/3.9/test/pin-pipenv-version-test-app/app.sh b/3.9/test/pin-pipenv-version-test-app/app.sh new file mode 100755 index 00000000..4334c760 --- /dev/null +++ b/3.9/test/pin-pipenv-version-test-app/app.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.9/test/pin-pipenv-version-test-app/requirements.txt b/3.9/test/pin-pipenv-version-test-app/requirements.txt new file mode 100644 index 00000000..66d42051 --- /dev/null +++ b/3.9/test/pin-pipenv-version-test-app/requirements.txt @@ -0,0 +1,2 @@ +uWSGI +Flask diff --git a/3.9/test/pin-pipenv-version-test-app/wsgi.py b/3.9/test/pin-pipenv-version-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.9/test/pin-pipenv-version-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() diff --git a/3.9/test/pipenv-test-app/testapp.py b/3.9/test/pipenv-test-app/testapp.py index a9193eec..fe200352 100644 --- a/3.9/test/pipenv-test-app/testapp.py +++ b/3.9/test/pipenv-test-app/testapp.py @@ -1,7 +1,12 @@ +import sys import requests def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) - assert requests.__version__ == '2.20.0' + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' return [b"Hello from gunicorn WSGI application!"] diff --git a/3.9/test/poetry-src-layout-test-app b/3.9/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.9/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.9/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.9/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..9bfe6709 --- /dev/null +++ b/3.9/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2021.5.29 diff --git a/3.9/test/pyuwsgi-pipenv-test-app/app.sh b/3.9/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.9/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/3.9/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.9/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.9/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.9/test/pyuwsgi-pipenv-test-app/wsgi.py b/3.9/test/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.9/test/pyuwsgi-pipenv-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() diff --git a/3.9/test/run b/3.9/test/run index 45e09ce7..134639eb 100755 --- a/3.9/test/run +++ b/3.9/test/run @@ -6,24 +6,51 @@ # 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 WEB_APPS=({standalone,setup,setup-requirements,django,numpy,app-home,npm-virtualenv-uwsgi,locale,mod-wsgi,pipenv,pipenv-and-micropipenv-should-fail,pin-pipenv-version,micropipenv,micropipenv-requirements}-test-app) +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,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${FULL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) # TODO: Make command compatible for Mac users -test_dir="$(readlink -zf $(dirname "${BASH_SOURCE[0]}"))" -image_dir=$(readlink -zf ${test_dir}/..) +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile +" if [[ -z $VERSION ]]; then echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 exit 1 fi -IMAGE_NAME=${IMAGE_NAME:-centos/python-${VERSION//./}-centos7} +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} . test/test-lib.sh -# TODO: This should be part of the image metadata -test_port=8080 - info() { echo -e "\n\e[1m[INFO] $@\e[0m\n" } @@ -49,7 +76,7 @@ run_s2i_build() { prepare() { if ! image_exists ${IMAGE_NAME}; then echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." - exit 1 + return 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. @@ -65,15 +92,6 @@ 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 @@ -81,23 +99,6 @@ cleanup() { fi rm -rf ${test_dir}/${1}/.git } - -check_result() { - # positive test & non-zero exit status = ERROR - # negative test & zero exit status = ERROR - local result="$1" - local type=${2:-positive} # if not defined, we expect possitive test type (zero exit code) - if [[ "$type" == "positive" && "$result" != "0" ]]; then - info "TEST FAILED (${type}), EXPECTED:0 GOT:${result}" - cleanup - exit $result - elif [[ "$type" == "negative" && "$result" == "0" ]]; then - info "TEST FAILED (${type}), EXPECTED: non-zero GOT:${result}" - cleanup - exit 1 - fi -} - wait_for_cid() { local max_attempts=10 local sleep_time=1 @@ -118,7 +119,7 @@ test_s2i_usage() { test_docker_run_usage() { info "Testing 'docker run' usage ..." - docker run ${IMAGE_NAME} &>/dev/null + docker run --rm ${IMAGE_NAME} &>/dev/null } test_scl_usage() { @@ -126,7 +127,7 @@ test_scl_usage() { local expected="$2" local cid_file="$3" - info "Testing the image SCL enable" + info "Testing command availability in the image" 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}'" @@ -166,7 +167,7 @@ test_connection() { } test_application() { - local cid_file=$(mktemp -u --suffix=.cid) + local cid_file="$CID_FILE_DIR"/"$(mktemp -u -p . --suffix .cid)" # Verify that the HTTP connection can be established to test application container run_test_application & @@ -174,70 +175,138 @@ test_application() { wait_for_cid # Some test apps have tests in their startup code so we have to check # that the container starts at all - check_result $? + ct_check_testcase_result $? - test_scl_usage "python --version" "Python $VERSION." "${cid_file}" - check_result $? + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? + ct_check_testcase_result $? test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? + ct_check_testcase_result $? test_connection - check_result $? - cleanup_app + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") } -test_latest_imagestreams() { - local result=1 - info "Testing the latest version in imagestreams" - # Switch to root directory of a container - pushd "${test_dir}/../.." >/dev/null || return 1 - ct_check_latest_imagestreams - result=$? - popd >/dev/null || return 1 - check_result $result +test_from_dockerfile(){ + info "Test from Dockerfile" + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? } -# Since we built the candidate image locally, we don't want S2I attempt to pull -# it from Docker hub -s2i_args="--pull-policy=never" +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" -# Verify the 'usage' script is working properly when running the base image with 's2i usage ...' -test_s2i_usage -check_result $? + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. -# Verify the 'usage' script is working properly when running the base image with 'docker run ...' -test_docker_run_usage -check_result $? + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? -# For debugging purposes, this script can be run with one or more arguments -# those arguments list is a sub-set of values in the WEB_APPS array defined above -# Example: ./run app-home-test-app pipenv-test-app -for app in ${@:-${WEB_APPS[@]}}; do - prepare ${app} - run_s2i_build ${app} - RESULT=$? - if [[ "$app" == *"-should-fail-"* ]]; then - # Tests with '-should-fail-' in their name should fail during a build, expecting non-zero exit status - check_result $RESULT "negative" - continue + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? else - check_result $RESULT + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." fi - # test application with default user - test_application +} +test_application_with_user() { # test application with random user CONTAINER_ARGS="--user 12345" test_application +} + +test_application_enable_init_wrapper() { # test application with init wrapper CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue - test_latest_imagestreams + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" - info "All tests for the ${app} finished successfully." cleanup ${app} done -info "All tests finished successfully." +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.9/test/run-openshift b/3.9/test/run-openshift deleted file mode 120000 index ec13e4a1..00000000 --- a/3.9/test/run-openshift +++ /dev/null @@ -1 +0,0 @@ -../../test/run-openshift \ No newline at end of file diff --git a/3.9/test/run-openshift-pytest b/3.9/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.9/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.9/test/run-pytest b/3.9/test/run-pytest new file mode 100755 index 00000000..9810fe89 --- /dev/null +++ b/3.9/test/run-pytest @@ -0,0 +1,16 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +if python3 -c 'import sys; sys.exit(0 if sys.version_info < (3,13) else 1)'; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.9/test/setup-cfg-test-app b/3.9/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.9/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/3.9/test/standalone-custom-pypi-index-test-app b/3.9/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.9/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/3.9/test/test-lib-openshift.sh b/3.9/test/test-lib-openshift.sh deleted file mode 120000 index 4f9f2996..00000000 --- a/3.9/test/test-lib-openshift.sh +++ /dev/null @@ -1 +0,0 @@ -../../common/test-lib-openshift.sh \ No newline at end of file diff --git a/3.9/test/test-lib-python.sh b/3.9/test/test-lib-python.sh deleted file mode 120000 index 0fa72132..00000000 --- a/3.9/test/test-lib-python.sh +++ /dev/null @@ -1 +0,0 @@ -../../test/test-lib-python.sh \ No newline at end of file diff --git a/3.9/test/test_container_application.py b/3.9/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.9/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.9/test/test_container_basics.py b/3.9/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.9/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.9/test/test_ocp_deploy_templates.py b/3.9/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.9/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.9/test/test_ocp_helm_python_django_app.py b/3.9/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.9/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.9/test/test_ocp_helm_python_django_psql_persistent.py b/3.9/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.9/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.9/test/test_ocp_helm_python_imagestreams.py b/3.9/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.9/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.9/test/test_ocp_imagestreams_quickstart.py b/3.9/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.9/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.9/test/test_ocp_python_ex_standalone.py b/3.9/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.9/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.9/test/test_ocp_python_ex_template.py b/3.9/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.9/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file diff --git a/Makefile b/Makefile index 22519841..e366bf6a 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ # Variables are documented in common/build.sh. BASE_IMAGE_NAME = python -VERSIONS = 2.7 3.6 3.7 3.8 3.9 +VERSIONS = 3.6 3.9 3.9-minimal 3.12 3.12-minimal 3.13 3.13-minimal 3.14 3.14-minimal OPENSHIFT_NAMESPACES = +DOCKER_BUILD_CONTEXT = .. # HACK: Ensure that 'git pull' for old clones doesn't cause confusion. # New clones should use '--recursive'. diff --git a/README.md b/README.md index 01db2539..42c46e6a 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ Python container images -==================== +======================= +[![Build and push container images to Quay.io registry](https://github.com/sclorg/s2i-python-container/actions/workflows/build-and-push.yml/badge.svg)](https://github.com/sclorg/s2i-python-container/actions/workflows/build-and-push.yml) This repository contains the source for building various versions of the Python application as a reproducible container image using [source-to-image](https://github.com/openshift/source-to-image). -Users can choose between RHEL, Fedora and CentOS based builder images. +Users can choose between RHEL, Fedora and CentOS Stream based builder images. The resulting image can be run using [podman](https://github.com/containers/libpod) or [docker](http://docker.io). For more information about using these images with OpenShift, please see the -official [OpenShift Documentation](https://docs.okd.io/latest/using_images/s2i_images/python.html). +official [OpenShift Documentation](https://docs.okd.io/latest/openshift_images/using_images/using-s21-images.html). For more information about concepts used in these container images, see the [Landing page](https://github.com/sclorg/welcome). @@ -28,7 +29,7 @@ A typical way how to contribute is: 1. Commit the changes. 1. Regenerate all files via `make generate-all`. 1. Commit generated files. -1. Test changes via `make test TARGET=fedora VERSIONS=3.7` which will `build`, `tag` and `test` an image in one step. +1. Test changes via `make test TARGET=fedora VERSIONS=3.9` which will `build`, `tag` and `test` an image in one step. 1. Open a pull request! For more information about contributing, see @@ -36,31 +37,35 @@ For more information about contributing, see Versions --------------- -Python versions currently provided are: -* [python-2.7](2.7) -* [python-3.6](3.6) -* [python-3.7](3.7) -* [python-3.8](3.8) - -RHEL versions currently supported are: -* RHEL 7 ([catalog.redhat.com](https://catalog.redhat.com/software/containers/search)) -* RHEL 8 ([catalog.redhat.com](https://catalog.redhat.com/software/containers/search)) - -CentOS versions currently supported are: -* CentOS 7 ([hub.docker.com](https://hub.docker.com/r/centos/)) - -Fedora versions currently supported are: -* Fedora 31 ([registry.fedoraproject.org](https://registry.fedoraproject.org/repo/f31/python3/tags/)) -* Fedora 32 ([registry.fedoraproject.org](https://registry.fedoraproject.org/repo/f32/python3/tags/)) +Currently supported versions are visible in the following table, expand an entry to see its container registry address. + +||CentOS Stream 9|CentOS Stream 10|Fedora|RHEL 8|RHEL 9|RHEL 10| +|:--|:--:|:--:|:--:|:--:|:--:|:--:| +|3.6||||
✓`registry.redhat.io/rhel8/python-36`
||| +|3.9|
✓`quay.io/sclorg/python-39-c9s`
||||
✓`registry.redhat.io/rhel9/python-39`
|| +|3.9-minimal|
✓`quay.io/sclorg/python-39-minimal-c9s`
|||||| +|3.12|
✓`quay.io/sclorg/python-312-c9s`
|
✓`quay.io/sclorg/python-312-c10s`
||
✓`registry.redhat.io/rhel8/python-312`
|
✓`registry.redhat.io/rhel9/python-312`
|| +|3.12-minimal|
✓`quay.io/sclorg/python-312-minimal-c9s`
|
✓`quay.io/sclorg/python-312-minimal-c10s`
||
✓`registry.redhat.io/rhel8/python-312-minimal`
|
✓`registry.redhat.io/rhel9/python-312-minimal`
|
✓`registry.redhat.io/rhel10/python-312-minimal`
| +|3.13||
✓`quay.io/sclorg/python-313-c10s`
|
✓`quay.io/fedora/python-313`
|||| +|3.13-minimal||
✓`quay.io/sclorg/python-313-minimal-c10s`
|
✓`quay.io/fedora/python-313-minimal`
|||| +|3.14|
✓`quay.io/sclorg/python-314-c9s`
||
✓`quay.io/fedora/python-314`
||
✓`registry.redhat.io/rhel9/python-314`
|| +|3.14-minimal|
✓`quay.io/sclorg/python-314-minimal-c9s`
|
✓`quay.io/sclorg/python-314-minimal-c10s`
|
✓`quay.io/fedora/python-314-minimal`
||
✓`registry.redhat.io/rhel9/python-314-minimal`
|
✓`registry.redhat.io/rhel10/python-314-minimal`
| + + +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. Download -------- To download one of the base Python images, follow the instructions you find in registries mentioned above. -For example, Centos image can be downloaded via: +For example, CentOS Stream image can be downloaded via: ``` -$ podman pull centos/python-36-centos7 +$ podman pull quay.io/c9s/python-39-c9s ``` Build @@ -70,7 +75,7 @@ To build a Python image from scratch run: ``` $ git clone https://github.com/sclorg/s2i-python-container.git $ cd s2i-python-container -$ make build TARGET=centos7 VERSIONS=3.6 +$ make build TARGET=c9s VERSIONS=3.9 ``` Where `TARGET` might be one of the supported platforms mentioned above. @@ -89,7 +94,7 @@ which launches tests to check functionality of simple Python applications built ``` $ cd s2i-python-container -$ make test TARGET=centos7 VERSIONS=3.6 +$ make test TARGET=c9s VERSIONS=3.9 ``` Where `TARGET` might be one of the supported platforms mentioned above. @@ -102,17 +107,17 @@ Repository organization ----------------------- * **``** - * **Dockerfile** + * **Dockerfile.c9s** - CentOS based Dockerfile. + CentOS Stream based Dockerfile. * **Dockerfile.fedora** Fedora based Dockerfile. - * **Dockerfile.rhel7** & **Dockerfile.rhel8** + * **Dockerfile.rhel8** & **Dockerfile.rhel9** - RHEL 7/8 based Dockerfile. In order to perform build or test actions on this + RHEL 8/9 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/`** @@ -142,4 +147,3 @@ Repository organization * **run** Script that runs the [S2I](https://github.com/openshift/source-to-image) test framework. - diff --git a/common b/common index d06e132d..498153e3 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit d06e132df8879355facfc10f0ff11a88e79f0345 +Subproject commit 498153e34eaa412f7bbb36ed134767e681c00ceb diff --git a/examples/app-module-test-app/.gitignore b/examples/app-module-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/examples/app-module-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/app-module-test-app/.s2i/environment b/examples/app-module-test-app/.s2i/environment new file mode 100644 index 00000000..ac798d75 --- /dev/null +++ b/examples/app-module-test-app/.s2i/environment @@ -0,0 +1 @@ +APP_MODULE=app:application diff --git a/3.3/test/setup-test-app/testapp.py b/examples/app-module-test-app/app.py similarity index 100% rename from 3.3/test/setup-test-app/testapp.py rename to examples/app-module-test-app/app.py diff --git a/examples/app-module-test-app/requirements.txt b/examples/app-module-test-app/requirements.txt new file mode 100644 index 00000000..268bd603 --- /dev/null +++ b/examples/app-module-test-app/requirements.txt @@ -0,0 +1,2 @@ +gunicorn<20.0.0; python_version < '3.5' +gunicorn>=20.0.0; python_version >= '3.5' \ No newline at end of file diff --git a/3.3/test/django-test-app/.gitignore b/examples/django-different-port-test-app/.gitignore similarity index 100% rename from 3.3/test/django-test-app/.gitignore rename to examples/django-different-port-test-app/.gitignore diff --git a/examples/django-different-port-test-app/.s2i/environment b/examples/django-different-port-test-app/.s2i/environment new file mode 100644 index 00000000..990affad --- /dev/null +++ b/examples/django-different-port-test-app/.s2i/environment @@ -0,0 +1 @@ +PORT=8085 diff --git a/3.3/test/django-test-app/manage.py b/examples/django-different-port-test-app/manage.py similarity index 100% rename from 3.3/test/django-test-app/manage.py rename to examples/django-different-port-test-app/manage.py diff --git a/3.3/test/app-home-test-app/project/__init__.py b/examples/django-different-port-test-app/project/__init__.py similarity index 100% rename from 3.3/test/app-home-test-app/project/__init__.py rename to examples/django-different-port-test-app/project/__init__.py diff --git a/3.3/test/django-test-app/project/settings.py b/examples/django-different-port-test-app/project/settings.py similarity index 65% rename from 3.3/test/django-test-app/project/settings.py rename to examples/django-different-port-test-app/project/settings.py index 3b5224b0..79072d09 100644 --- a/3.3/test/django-test-app/project/settings.py +++ b/examples/django-different-port-test-app/project/settings.py @@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/1.8/ref/settings/ """ +import django # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os @@ -25,7 +26,8 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +# SECURITY WARNING: do not use '*' on production or use some HTTP(S) proxy +ALLOWED_HOSTS = ['*'] # Application definition @@ -39,16 +41,29 @@ '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', -) +# Django 1 +if django.VERSION[0] == 1: + 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', + ) +else: + # Django 2+ + MIDDLEWARE = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', + ) ROOT_URLCONF = 'project.urls' diff --git a/3.3/test/django-test-app/project/urls.py b/examples/django-different-port-test-app/project/urls.py similarity index 70% rename from 3.3/test/django-test-app/project/urls.py rename to examples/django-different-port-test-app/project/urls.py index b5e472c9..dc5093d8 100644 --- a/3.3/test/django-test-app/project/urls.py +++ b/examples/django-different-port-test-app/project/urls.py @@ -13,9 +13,15 @@ 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 +# django.conf.urls.url() has been deprecated in Django 3.0 and removed from Django 4.0+ +import django.conf.urls +if hasattr(django.conf.urls, 'url'): + from django.conf.urls import url +else: + from django.urls import re_path as url + urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ] diff --git a/3.3/test/django-test-app/project/wsgi.py b/examples/django-different-port-test-app/project/wsgi.py similarity index 100% rename from 3.3/test/django-test-app/project/wsgi.py rename to examples/django-different-port-test-app/project/wsgi.py diff --git a/examples/django-different-port-test-app/requirements.txt b/examples/django-different-port-test-app/requirements.txt new file mode 100644 index 00000000..d5de07e5 --- /dev/null +++ b/examples/django-different-port-test-app/requirements.txt @@ -0,0 +1,3 @@ +Django>=3,<5; python_version >='3.12' +Django>=2,<3; python_version >='3.9' and python_version <'3.12' +Django<2; python_version <'3.9' \ No newline at end of file diff --git a/examples/django-postgresql-persistent.json b/examples/django-postgresql-persistent.json index e7a7e118..1440cd1b 100644 --- a/examples/django-postgresql-persistent.json +++ b/examples/django-postgresql-persistent.json @@ -1,6 +1,6 @@ { "kind": "Template", - "apiVersion": "v1", + "apiVersion": "template.openshift.io/v1", "metadata": { "name": "django-psql-persistent", "annotations": { @@ -56,7 +56,7 @@ }, { "kind": "Route", - "apiVersion": "v1", + "apiVersion": "route.openshift.io/v1", "metadata": { "name": "${NAME}", "annotations": { @@ -73,7 +73,7 @@ }, { "kind": "ImageStream", - "apiVersion": "v1", + "apiVersion": "image.openshift.io/v1", "metadata": { "name": "${NAME}", "annotations": { @@ -83,7 +83,7 @@ }, { "kind": "BuildConfig", - "apiVersion": "v1", + "apiVersion": "build.openshift.io/v1", "metadata": { "name": "${NAME}", "annotations": { @@ -142,40 +142,25 @@ } }, { - "kind": "DeploymentConfig", - "apiVersion": "v1", + "kind": "Deployment", + "apiVersion": "apps/v1", "metadata": { "name": "${NAME}", "annotations": { "description": "Defines how to deploy the application server", - "template.alpha.openshift.io/wait-for-ready": "true" + "template.alpha.openshift.io/wait-for-ready": "true", + "image.openshift.io/triggers": "[{\"from\":{\"kind\":\"ImageStreamTag\",\"name\":\"${NAME}:latest\"},\"fieldPath\": \"spec.template.spec.containers[0].image\"}]" } }, "spec": { "strategy": { "type": "Recreate" }, - "triggers": [ - { - "type": "ImageChange", - "imageChangeParams": { - "automatic": true, - "containerNames": [ - "django-psql-persistent" - ], - "from": { - "kind": "ImageStreamTag", - "name": "${NAME}:latest" - } - } - }, - { - "type": "ConfigChange" - } - ], "replicas": 1, "selector": { - "name": "${NAME}" + "matchLabels": { + "name": "${NAME}" + } }, "template": { "metadata": { @@ -306,41 +291,25 @@ } }, { - "kind": "DeploymentConfig", - "apiVersion": "v1", + "kind": "Deployment", + "apiVersion": "apps/v1", "metadata": { "name": "${DATABASE_SERVICE_NAME}", "annotations": { "description": "Defines how to deploy the database", - "template.alpha.openshift.io/wait-for-ready": "true" + "template.alpha.openshift.io/wait-for-ready": "true", + "image.openshift.io/triggers": "[{\"from\":{\"kind\":\"ImageStreamTag\",\"name\":\"postgresql:12\"},\"fieldPath\": \"spec.template.spec.containers[0].image\"}]" } }, "spec": { "strategy": { "type": "Recreate" }, - "triggers": [ - { - "type": "ImageChange", - "imageChangeParams": { - "automatic": true, - "containerNames": [ - "postgresql" - ], - "from": { - "kind": "ImageStreamTag", - "namespace": "${NAMESPACE}", - "name": "postgresql:9.5" - } - } - }, - { - "type": "ConfigChange" - } - ], "replicas": 1, "selector": { - "name": "${DATABASE_SERVICE_NAME}" + "matchLabels":{ + "name": "${DATABASE_SERVICE_NAME}" + } }, "template": { "metadata": { diff --git a/examples/django-postgresql.json b/examples/django-postgresql.json index 542c36d5..9b50e715 100644 --- a/examples/django-postgresql.json +++ b/examples/django-postgresql.json @@ -1,6 +1,6 @@ { "kind": "Template", - "apiVersion": "v1", + "apiVersion": "template.openshift.io/v1", "metadata": { "name": "django-psql-example", "annotations": { @@ -56,7 +56,7 @@ }, { "kind": "Route", - "apiVersion": "v1", + "apiVersion": "route.openshift.io/v1", "metadata": { "name": "${NAME}", "annotations": { @@ -73,7 +73,7 @@ }, { "kind": "ImageStream", - "apiVersion": "v1", + "apiVersion": "image.openshift.io/v1", "metadata": { "name": "${NAME}", "annotations": { @@ -83,7 +83,7 @@ }, { "kind": "BuildConfig", - "apiVersion": "v1", + "apiVersion": "build.openshift.io/v1", "metadata": { "name": "${NAME}", "annotations": { @@ -142,40 +142,26 @@ } }, { - "kind": "DeploymentConfig", - "apiVersion": "v1", + "kind": "Deployment", + "apiVersion": "apps/v1", "metadata": { "name": "${NAME}", "annotations": { "description": "Defines how to deploy the application server", - "template.alpha.openshift.io/wait-for-ready": "true" + "template.alpha.openshift.io/wait-for-ready": "true", + "image.openshift.io/triggers": "[{\"from\":{\"kind\":\"ImageStreamTag\",\"name\":\"${NAME}:latest\"},\"fieldPath\": \"spec.template.spec.containers[0].image\"}]" + } }, "spec": { "strategy": { "type": "Recreate" }, - "triggers": [ - { - "type": "ImageChange", - "imageChangeParams": { - "automatic": true, - "containerNames": [ - "django-psql-example" - ], - "from": { - "kind": "ImageStreamTag", - "name": "${NAME}:latest" - } - } - }, - { - "type": "ConfigChange" - } - ], "replicas": 1, "selector": { - "name": "${NAME}" + "matchLabels": { + "name": "${NAME}" + } }, "template": { "metadata": { @@ -289,41 +275,25 @@ } }, { - "kind": "DeploymentConfig", - "apiVersion": "v1", + "kind": "Deployment", + "apiVersion": "apps/v1", "metadata": { "name": "${DATABASE_SERVICE_NAME}", "annotations": { "description": "Defines how to deploy the database", - "template.alpha.openshift.io/wait-for-ready": "true" + "template.alpha.openshift.io/wait-for-ready": "true", + "image.openshift.io/triggers": "[{\"from\":{\"kind\":\"ImageStreamTag\",\"name\":\"postgresql:12\"},\"fieldPath\": \"spec.template.spec.containers[0].image\"}]" } }, "spec": { "strategy": { "type": "Recreate" }, - "triggers": [ - { - "type": "ImageChange", - "imageChangeParams": { - "automatic": true, - "containerNames": [ - "postgresql" - ], - "from": { - "kind": "ImageStreamTag", - "namespace": "${NAMESPACE}", - "name": "postgresql:9.5" - } - } - }, - { - "type": "ConfigChange" - } - ], "replicas": 1, "selector": { - "name": "${DATABASE_SERVICE_NAME}" + "matchLabels":{ + "name": "${DATABASE_SERVICE_NAME}" + } }, "template": { "metadata": { diff --git a/examples/django-test-app/project/settings.py b/examples/django-test-app/project/settings.py index 5827794f..79072d09 100644 --- a/examples/django-test-app/project/settings.py +++ b/examples/django-test-app/project/settings.py @@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/1.8/ref/settings/ """ +import django # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os @@ -40,16 +41,29 @@ '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', -) +# Django 1 +if django.VERSION[0] == 1: + 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', + ) +else: + # Django 2+ + MIDDLEWARE = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', + ) ROOT_URLCONF = 'project.urls' diff --git a/examples/django-test-app/project/urls.py b/examples/django-test-app/project/urls.py index b5e472c9..7c1d8e8c 100644 --- a/examples/django-test-app/project/urls.py +++ b/examples/django-test-app/project/urls.py @@ -13,9 +13,16 @@ 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 +# django.conf.urls.url() has been deprecated in Django 3.0 and removed from Django 4.0+ +import django.conf.urls +if hasattr(django.conf.urls, 'url'): + from django.conf.urls import url +else: + from django.urls import re_path as url + + urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ] diff --git a/examples/django-test-app/requirements.txt b/examples/django-test-app/requirements.txt index 30eb6217..6603dffb 100644 --- a/examples/django-test-app/requirements.txt +++ b/examples/django-test-app/requirements.txt @@ -1 +1,3 @@ -Django==1.11.29 +Django>=3,<5; python_version >='3.12' +Django>=2,<3; python_version >='3.9' and python_version <'3.12' +Django<2; python_version <'3.9' diff --git a/examples/gunicorn-config-different-port-test-app/.gitignore b/examples/gunicorn-config-different-port-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/examples/gunicorn-config-different-port-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-config-different-port-test-app/.s2i/environment b/examples/gunicorn-config-different-port-test-app/.s2i/environment new file mode 100644 index 00000000..d975f74c --- /dev/null +++ b/examples/gunicorn-config-different-port-test-app/.s2i/environment @@ -0,0 +1,2 @@ +APP_MODULE=app:application +APP_CONFIG=./gunicorn.conf.py diff --git a/examples/gunicorn-config-different-port-test-app/app.py b/examples/gunicorn-config-different-port-test-app/app.py new file mode 100644 index 00000000..09fee1b7 --- /dev/null +++ b/examples/gunicorn-config-different-port-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-config-different-port-test-app/gunicorn.conf.py b/examples/gunicorn-config-different-port-test-app/gunicorn.conf.py new file mode 100644 index 00000000..1ae04466 --- /dev/null +++ b/examples/gunicorn-config-different-port-test-app/gunicorn.conf.py @@ -0,0 +1,4 @@ +import multiprocessing + +bind = "0.0.0.0:8085" +workers = multiprocessing.cpu_count() * 2 + 1 diff --git a/examples/gunicorn-config-different-port-test-app/requirements.txt b/examples/gunicorn-config-different-port-test-app/requirements.txt new file mode 100644 index 00000000..268bd603 --- /dev/null +++ b/examples/gunicorn-config-different-port-test-app/requirements.txt @@ -0,0 +1,2 @@ +gunicorn<20.0.0; python_version < '3.5' +gunicorn>=20.0.0; python_version >= '3.5' \ No newline at end of file diff --git a/examples/gunicorn-different-port-test-app/.gitignore b/examples/gunicorn-different-port-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/examples/gunicorn-different-port-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-different-port-test-app/.s2i/environment b/examples/gunicorn-different-port-test-app/.s2i/environment new file mode 100644 index 00000000..164666b4 --- /dev/null +++ b/examples/gunicorn-different-port-test-app/.s2i/environment @@ -0,0 +1,2 @@ +APP_MODULE=app:application +PORT=8085 diff --git a/examples/gunicorn-different-port-test-app/app.py b/examples/gunicorn-different-port-test-app/app.py new file mode 100644 index 00000000..09fee1b7 --- /dev/null +++ b/examples/gunicorn-different-port-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-different-port-test-app/requirements.txt b/examples/gunicorn-different-port-test-app/requirements.txt new file mode 100644 index 00000000..268bd603 --- /dev/null +++ b/examples/gunicorn-different-port-test-app/requirements.txt @@ -0,0 +1,2 @@ +gunicorn<20.0.0; python_version < '3.5' +gunicorn>=20.0.0; python_version >= '3.5' \ No newline at end of file diff --git a/examples/gunicorn-python-configfile-different-port-test-app/.gitignore b/examples/gunicorn-python-configfile-different-port-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/examples/gunicorn-python-configfile-different-port-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-python-configfile-different-port-test-app/app.py b/examples/gunicorn-python-configfile-different-port-test-app/app.py new file mode 100644 index 00000000..09fee1b7 --- /dev/null +++ b/examples/gunicorn-python-configfile-different-port-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-python-configfile-different-port-test-app/gunicorn.conf.py b/examples/gunicorn-python-configfile-different-port-test-app/gunicorn.conf.py new file mode 100644 index 00000000..bcb267f6 --- /dev/null +++ b/examples/gunicorn-python-configfile-different-port-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-python-configfile-different-port-test-app/requirements.txt b/examples/gunicorn-python-configfile-different-port-test-app/requirements.txt new file mode 100644 index 00000000..8819996f --- /dev/null +++ b/examples/gunicorn-python-configfile-different-port-test-app/requirements.txt @@ -0,0 +1 @@ +gunicorn>=20.1.0 diff --git a/examples/micropipenv-requirements-test-app/requirements.txt b/examples/micropipenv-requirements-test-app/requirements.txt index a74adef8..58035c4b 100644 --- a/examples/micropipenv-requirements-test-app/requirements.txt +++ b/examples/micropipenv-requirements-test-app/requirements.txt @@ -1,2 +1,6 @@ -mod_wsgi +mod_wsgi==5.0.1; python_version >= '3.12' +mod_wsgi<5.0.0; python_version < '3.12' Flask +# Tests that gssapi package is installable. +# This test is ideal place because it's skipped for minimal containers. +gssapi \ No newline at end of file diff --git a/examples/micropipenv-test-app/Pipfile b/examples/micropipenv-test-app/Pipfile index 2ab0cfa5..fc4d0c80 100644 --- a/examples/micropipenv-test-app/Pipfile +++ b/examples/micropipenv-test-app/Pipfile @@ -5,10 +5,11 @@ name = "pypi" [packages] "e1839a8" = {path = ".", editable = true} -requests = "==2.20.0" +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} [dev-packages] pytest = ">=2.8.0" [requires] -python_version = "3.7" +python_version = "3.13" diff --git a/examples/micropipenv-test-app/Pipfile.lock b/examples/micropipenv-test-app/Pipfile.lock index d5a6f9be..b0aa1027 100644 --- a/examples/micropipenv-test-app/Pipfile.lock +++ b/examples/micropipenv-test-app/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "dbe9d3f790514385bed543a5fc1e85759ad0d0e39028c102d82c8d4afc1a5fc3" + "sha256": "1c042fcc7ce0692ac9ad83e0cc00f01a81991e88b45c33d6edc3e55ac79ae443" }, "pipfile-spec": 6, "requires": { - "python_version": "3.7" + "python_version": "3.13" }, "sources": [ { @@ -18,138 +18,194 @@ "default": { "certifi": { "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" - ], - "version": "==2019.9.11" + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" }, - "chardet": { + "gunicorn": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" ], - "version": "==3.0.4" - }, - "e1839a8": { - "editable": true, - "path": "." + "markers": "python_version >= '3.7'", + "version": "==23.0.0" }, - "gunicorn": { + "idna": { "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "version": "==19.9.0" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, - "idna": { + "packaging": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" ], - "version": "==2.7" + "markers": "python_version >= '3.8'", + "version": "==24.1" }, "requests": { "hashes": [ - "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", - "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" ], "index": "pypi", - "version": "==2.20.0" + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" }, - "urllib3": { - "hashes": [ - "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", - "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" - ], - "version": "==1.24.3" - } - }, - "develop": { - "atomicwrites": { + "setuptools": { "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" ], - "version": "==1.3.0" + "markers": "python_version >= '3.12'", + "version": "==67.7.2" }, - "attrs": { - "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" - ], - "version": "==19.3.0" + "testapp": { + "version": "==0.1" }, - "importlib-metadata": { + "urllib3": { "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" ], - "markers": "python_version < '3.8'", - "version": "==0.23" - }, - "more-itertools": { + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" ], - "version": "==7.2.0" + "markers": "python_version >= '3.7'", + "version": "==2.0.0" }, "packaging": { "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" ], - "version": "==19.2" + "markers": "python_version >= '3.8'", + "version": "==24.1" }, "pluggy": { "hashes": [ - "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", - "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" - ], - "version": "==0.13.0" - }, - "py": { - "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", + "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" ], - "version": "==1.8.0" - }, - "pyparsing": { - "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" - ], - "version": "==2.4.2" + "markers": "python_version >= '3.8'", + "version": "==1.5.0" }, "pytest": { "hashes": [ - "sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", - "sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4" + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" ], "index": "pypi", - "version": "==5.2.2" - }, - "six": { - "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" - ], - "version": "==1.12.0" - }, - "wcwidth": { - "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" - ], - "version": "==0.1.7" - }, - "zipp": { - "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" - ], - "version": "==0.6.0" + "markers": "python_version >= '3.8'", + "version": "==8.3.3" } } } diff --git a/examples/micropipenv-test-app/testapp.py b/examples/micropipenv-test-app/testapp.py index a9193eec..fe200352 100644 --- a/examples/micropipenv-test-app/testapp.py +++ b/examples/micropipenv-test-app/testapp.py @@ -1,7 +1,12 @@ +import sys import requests def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) - assert requests.__version__ == '2.20.0' + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' return [b"Hello from gunicorn WSGI application!"] diff --git a/examples/mod-wsgi-test-app/app.py b/examples/mod-wsgi-test-app/app.py index bbac2641..bce8b2dc 100644 --- a/examples/mod-wsgi-test-app/app.py +++ b/examples/mod-wsgi-test-app/app.py @@ -1,7 +1,10 @@ import os -import mod_wsgi.server +try: + import mod_wsgi.express.cli as server_wrapper +except ImportError: + import mod_wsgi.server as server_wrapper -mod_wsgi.server.start( +server_wrapper.start( '--log-to-terminal', '--port', '8080', '--trust-proxy-header', 'X-Forwarded-For', diff --git a/examples/mod-wsgi-test-app/requirements.txt b/examples/mod-wsgi-test-app/requirements.txt index a74adef8..e74c313b 100644 --- a/examples/mod-wsgi-test-app/requirements.txt +++ b/examples/mod-wsgi-test-app/requirements.txt @@ -1,2 +1,3 @@ -mod_wsgi +mod_wsgi>=5.0.0; python_version >= '3.12' +mod_wsgi<5.0.0; python_version < '3.12' Flask diff --git a/examples/npm-virtualenv-uwsgi-test-app/app.sh b/examples/npm-virtualenv-uwsgi-test-app/app.sh index f34357ac..2272c397 100755 --- a/examples/npm-virtualenv-uwsgi-test-app/app.sh +++ b/examples/npm-virtualenv-uwsgi-test-app/app.sh @@ -13,9 +13,11 @@ packages=("pip" "setuptools" "wheel") for pkg in ${packages[@]}; do # grep returns exit code 1 if the output contains only one line starting # with "Requirement already …" which means that the package is updated - python -m pip install -U --no-deps --no-python-version-warning $pkg 2>&1 | grep -v "^Requirement already up-to-date: " + python -m pip install -U --no-deps $pkg 2>&1 | grep -Ev "^Requirement already (up-to-date|satisfied): " if [ $? -eq 0 ]; then echo "ERROR: Failed to upgrade '$pkg' to the latest version." + echo "Output of the pip install command is:" + python -m pip install -U --no-deps $pkg exit 1 fi done @@ -36,7 +38,7 @@ if [ $? -ne 0 ]; then fi # Use the mkdirp module to create a directory and it's subdirectory -node -e "var mkdirp = require('mkdirp'); mkdirp('dir1/dir2')" +node -e "const { mkdirp } = require('mkdirp'); mkdirp('dir1/dir2')" if [ $? -ne 0 ]; then echo "ERROR: Nodejs cannot use the freshly installed 'mkdirp' module." exit 1 diff --git a/examples/numpy-test-app/requirements.txt b/examples/numpy-test-app/requirements.txt index e8dd2c3d..a3b79fff 100644 --- a/examples/numpy-test-app/requirements.txt +++ b/examples/numpy-test-app/requirements.txt @@ -1,4 +1,6 @@ -gunicorn<20.0.0; python_version < '3.5' gunicorn>=20.0.0; python_version >= '3.5' -numpy<1.17.0; python_version < '3.5' -numpy>=1.17.0; python_version >= '3.5' +# Numpy 1.23.0 no longer builds from source on very old GCC toolchains +numpy<1.23.0; python_version >= '3.6' and python_version < '3.11' + +# numpy 1.23.5 is the first one with wheels for Python 3.11 +numpy>1.23.5; python_version >= "3.11" diff --git a/examples/pin-pipenv-version-test-app/.s2i/environment b/examples/pin-pipenv-version-test-app/.s2i/environment index 6440a428..530e67f1 100644 --- a/examples/pin-pipenv-version-test-app/.s2i/environment +++ b/examples/pin-pipenv-version-test-app/.s2i/environment @@ -1,2 +1,2 @@ ENABLE_PIPENV=1 -PIN_PIPENV_VERSION=2018.11.26 +PIN_PIPENV_VERSION=2026.0.3 diff --git a/examples/pin-pipenv-version-test-app/app.sh b/examples/pin-pipenv-version-test-app/app.sh index b6d71a09..4334c760 100755 --- a/examples/pin-pipenv-version-test-app/app.sh +++ b/examples/pin-pipenv-version-test-app/app.sh @@ -1,8 +1,10 @@ #!/bin/bash +source .s2i/environment + echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." pipenv_version=`pipenv --version` -expected="pipenv, version 2018.11.26" +expected="pipenv, version $PIN_PIPENV_VERSION" if [ "$pipenv_version" != "$expected" ]; then echo "ERROR: pipenv version is different than expected." echo "Expected: ${expected}" diff --git a/examples/pipenv-test-app/Pipfile b/examples/pipenv-test-app/Pipfile index 117b223d..45aee705 100644 --- a/examples/pipenv-test-app/Pipfile +++ b/examples/pipenv-test-app/Pipfile @@ -5,10 +5,11 @@ name = "pypi" [packages] "e1839a8" = {path = ".", editable = true} -requests = "==2.20.0" +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} [dev-packages] pytest = ">=2.8.0" [requires] -python_version = "3.9" +python_version = "3.14" diff --git a/examples/pipenv-test-app/Pipfile.lock b/examples/pipenv-test-app/Pipfile.lock index 25741732..11c134ec 100644 --- a/examples/pipenv-test-app/Pipfile.lock +++ b/examples/pipenv-test-app/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "9d47e9d2d35583e081cf783e9421e75fafb102d07969ad6f74546f48db3a8101" + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" }, "pipfile-spec": 6, "requires": { - "python_version": "3.9" + "python_version": "3.14" }, "sources": [ { @@ -18,128 +18,225 @@ "default": { "certifi": { "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" ], - "version": "==2020.6.20" + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" }, - "chardet": { + "charset-normalizer": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" ], - "version": "==3.0.4" - }, - "e1839a8": { - "editable": true, - "path": "." + "markers": "python_version >= '3.7'", + "version": "==3.4.4" }, "gunicorn": { "hashes": [ - "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", - "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" ], - "markers": "python_version >= '3.4'", - "version": "==20.0.4" + "markers": "python_version >= '3.7'", + "version": "==23.0.0" }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], - "version": "==2.7" + "markers": "python_version >= '3.8'", + "version": "==25.0" }, "requests": { "hashes": [ - "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", - "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" ], "index": "pypi", - "version": "==2.20.0" + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" }, "testapp": { - "editable": true, - "path": "." + "version": "==0.1" }, "urllib3": { "hashes": [ - "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", - "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'", - "version": "==1.24.3" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" } }, "develop": { - "attrs": { - "hashes": [ - "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", - "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.2.0" - }, "iniconfig": { "hashes": [ - "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437", - "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69" + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" ], - "version": "==1.0.1" + "markers": "python_version >= '3.10'", + "version": "==2.3.0" }, "packaging": { "hashes": [ - "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", - "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.4" + "markers": "python_version >= '3.8'", + "version": "==25.0" }, "pluggy": { "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.13.1" + "markers": "python_version >= '3.9'", + "version": "==1.6.0" }, - "py": { + "pygments": { "hashes": [ - "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", - "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.9.0" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.7" + "markers": "python_version >= '3.8'", + "version": "==2.19.2" }, "pytest": { "hashes": [ - "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9", - "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92" + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" ], "index": "pypi", - "version": "==6.1.1" - }, - "six": { - "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.15.0" - }, - "toml": { - "hashes": [ - "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", - "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" - ], - "version": "==0.10.1" + "markers": "python_version >= '3.10'", + "version": "==9.0.2" } } } diff --git a/examples/pipenv-test-app/testapp.py b/examples/pipenv-test-app/testapp.py index a9193eec..fe200352 100644 --- a/examples/pipenv-test-app/testapp.py +++ b/examples/pipenv-test-app/testapp.py @@ -1,7 +1,12 @@ +import sys import requests def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) - assert requests.__version__ == '2.20.0' + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' return [b"Hello from gunicorn WSGI application!"] diff --git a/examples/poetry-src-layout-test-app/.s2i/environment b/examples/poetry-src-layout-test-app/.s2i/environment new file mode 100644 index 00000000..e6e3d481 --- /dev/null +++ b/examples/poetry-src-layout-test-app/.s2i/environment @@ -0,0 +1 @@ +APP_MODULE="poetry_src_layout:application" diff --git a/examples/poetry-src-layout-test-app/poetry.lock b/examples/poetry-src-layout-test-app/poetry.lock new file mode 100644 index 00000000..ba15cadf --- /dev/null +++ b/examples/poetry-src-layout-test-app/poetry.lock @@ -0,0 +1,24 @@ +[[package]] +name = "gunicorn" +version = "20.1.0" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.6" +content-hash = "e0a7b0bc1c0f3d7d6d5781929434419af5258f2e5f831782dc5e02af1e7010d5" + +[metadata.files] +gunicorn = [ + {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, + {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, +] diff --git a/examples/poetry-src-layout-test-app/pyproject.toml b/examples/poetry-src-layout-test-app/pyproject.toml new file mode 100644 index 00000000..09d17324 --- /dev/null +++ b/examples/poetry-src-layout-test-app/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "poetry-src-layout" +version = "0.1.0" +description = "" +authors = ["Your Name "] +packages = [{include = "poetry_src_layout", from = "src"}] + +[tool.poetry.dependencies] +python = "^3.6" +gunicorn = "*" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/examples/poetry-src-layout-test-app/src/poetry_src_layout/__init__.py b/examples/poetry-src-layout-test-app/src/poetry_src_layout/__init__.py new file mode 100644 index 00000000..1904a2f9 --- /dev/null +++ b/examples/poetry-src-layout-test-app/src/poetry_src_layout/__init__.py @@ -0,0 +1,3 @@ +def application(environ, start_response): + start_response('200 OK', [('Content-Type','text/plain')]) + return [b"Hello from gunicorn WSGI application!"] diff --git a/examples/pyuwsgi-pipenv-test-app/.s2i/environment b/examples/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..530e67f1 --- /dev/null +++ b/examples/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2026.0.3 diff --git a/examples/pyuwsgi-pipenv-test-app/app.sh b/examples/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/examples/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# 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/examples/pyuwsgi-pipenv-test-app/requirements.txt b/examples/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/examples/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/examples/pyuwsgi-pipenv-test-app/wsgi.py b/examples/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/examples/pyuwsgi-pipenv-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() diff --git a/examples/setup-cfg-test-app/pyproject.toml b/examples/setup-cfg-test-app/pyproject.toml new file mode 100644 index 00000000..374b58cb --- /dev/null +++ b/examples/setup-cfg-test-app/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel" +] +build-backend = "setuptools.build_meta" diff --git a/examples/setup-cfg-test-app/setup.cfg b/examples/setup-cfg-test-app/setup.cfg new file mode 100644 index 00000000..a8bedfdd --- /dev/null +++ b/examples/setup-cfg-test-app/setup.cfg @@ -0,0 +1,14 @@ +[metadata] +name = testapp +version = 0.1 + +[options] +install_requires = + gunicorn<20.0.0; python_version < '3.5' + gunicorn>=20.0.0; python_version >= '3.5' +package_dir = + = . +packages = find: + +[options.packages.find] +where = . diff --git a/3.3/test/app-home-test-app/project/wsgi.py b/examples/setup-cfg-test-app/wsgi.py similarity index 63% rename from 3.3/test/app-home-test-app/project/wsgi.py rename to examples/setup-cfg-test-app/wsgi.py index 576a05b8..09fee1b7 100644 --- a/3.3/test/app-home-test-app/project/wsgi.py +++ b/examples/setup-cfg-test-app/wsgi.py @@ -1,3 +1,4 @@ + def application(environ, start_response): start_response('200 OK', [('Content-Type','text/plain')]) - return [b"Hello World from app-home WSGI application!"] + return [b"Hello from gunicorn WSGI application!"] diff --git a/examples/setup-requirements-test-app/requirements.txt b/examples/setup-requirements-test-app/requirements.txt index 9368e485..40b7e536 100644 --- a/examples/setup-requirements-test-app/requirements.txt +++ b/examples/setup-requirements-test-app/requirements.txt @@ -1,3 +1,4 @@ -e . gunicorn<20.0.0; python_version < '3.5' -gunicorn>=20.0.0; python_version >= '3.5' \ No newline at end of file +gunicorn>=20.0.0; python_version >= '3.5' +setuptools \ No newline at end of file diff --git a/examples/setup-test-app/requirements.txt b/examples/setup-test-app/requirements.txt index 268bd603..37dfec99 100644 --- a/examples/setup-test-app/requirements.txt +++ b/examples/setup-test-app/requirements.txt @@ -1,2 +1,3 @@ gunicorn<20.0.0; python_version < '3.5' -gunicorn>=20.0.0; python_version >= '3.5' \ No newline at end of file +gunicorn>=20.0.0; python_version >= '3.5' +setuptools \ No newline at end of file diff --git a/examples/standalone-custom-pypi-index-test-app/.gitignore b/examples/standalone-custom-pypi-index-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/examples/standalone-custom-pypi-index-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/standalone-custom-pypi-index-test-app/.s2i/environment b/examples/standalone-custom-pypi-index-test-app/.s2i/environment new file mode 100644 index 00000000..25d96d59 --- /dev/null +++ b/examples/standalone-custom-pypi-index-test-app/.s2i/environment @@ -0,0 +1,2 @@ +PIP_INDEX_URL=https://test.pypi.org/simple/ +DISABLE_SETUP_PY_PROCESSING=true diff --git a/examples/standalone-custom-pypi-index-test-app/app.py b/examples/standalone-custom-pypi-index-test-app/app.py new file mode 100644 index 00000000..a942e3bd --- /dev/null +++ b/examples/standalone-custom-pypi-index-test-app/app.py @@ -0,0 +1,23 @@ +import http.server +import signal +import socketserver +import sys + +PORT = 8080 + +def sigterm_handler(signum, frame): + print("SIGTERM received, performing cleanup...") + sys.exit(0) + +signal.signal(signal.SIGTERM, sigterm_handler) + +class SimpleHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write(b"Hello, World!") + +with socketserver.TCPServer(('', PORT), SimpleHTTPRequestHandler) as httpd: + print("serving at port", PORT) + httpd.serve_forever() diff --git a/examples/standalone-custom-pypi-index-test-app/app.sh b/examples/standalone-custom-pypi-index-test-app/app.sh new file mode 100755 index 00000000..bb4b5cd4 --- /dev/null +++ b/examples/standalone-custom-pypi-index-test-app/app.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +echo "Testing pello package installed version..." + +if pip freeze | grep Pello==1.0.3; then + echo "Pello 1.0.3 is installed, starting webserver..." + exec python app.py +else + echo "Pello 1.0.3 wasn't installed, exiting..." + exit 1 +fi diff --git a/examples/standalone-custom-pypi-index-test-app/requirements.txt b/examples/standalone-custom-pypi-index-test-app/requirements.txt new file mode 100644 index 00000000..e9ad401d --- /dev/null +++ b/examples/standalone-custom-pypi-index-test-app/requirements.txt @@ -0,0 +1 @@ +pello diff --git a/examples/uwsgi-test-app/.s2i/environment b/examples/uwsgi-test-app/.s2i/environment new file mode 100644 index 00000000..c1761606 --- /dev/null +++ b/examples/uwsgi-test-app/.s2i/environment @@ -0,0 +1 @@ +UPGRADE_PIP_TO_LATEST=1 diff --git a/examples/uwsgi-test-app/app.sh b/examples/uwsgi-test-app/app.sh new file mode 100755 index 00000000..bc86dbca --- /dev/null +++ b/examples/uwsgi-test-app/app.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# 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 that the virtual environment is active ..." +if ! export | grep -q 'VIRTUAL_ENV="/opt/app-root"'; then + echo "VIRTUAL_ENV is not set properly!" + export + exit 1 +fi + + +echo "Testing UPGRADE_PIP_TO_LATEST=1 (set in .s2i/environment) ..." +packages=("pip" "setuptools" "wheel") +for pkg in ${packages[@]}; do + # grep returns exit code 1 if the output contains only one line starting + # with "Requirement already …" which means that the package is updated + python -m pip install -U --no-deps $pkg 2>&1 | grep -Ev "^Requirement already (up-to-date|satisfied): " + if [ $? -eq 0 ]; then + echo "ERROR: Failed to upgrade '$pkg' to the latest version." + exit 1 + fi +done + +# 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/examples/uwsgi-test-app/requirements.txt b/examples/uwsgi-test-app/requirements.txt new file mode 100644 index 00000000..66d42051 --- /dev/null +++ b/examples/uwsgi-test-app/requirements.txt @@ -0,0 +1,2 @@ +uWSGI +Flask diff --git a/examples/uwsgi-test-app/wsgi.py b/examples/uwsgi-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/examples/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() diff --git a/imagestreams/imagestreams.yaml b/imagestreams/imagestreams.yaml new file mode 100644 index 00000000..9c1a5144 --- /dev/null +++ b/imagestreams/imagestreams.yaml @@ -0,0 +1,48 @@ +--- +- name: python + pretty_name: Python + sample_repo: https://github.com/sclorg/django-ex.git + category: builder + description: >- + Build and run Python APP_VERSION applications on DISTRO_NAME. + For more information about using this builder image, + including OpenShift considerations, see + https://github.com/sclorg/s2i-python-container/blob/master/APP_VERSION/README.md. + imagestream_files: + - filename: python-centos.json + latest: "3.12-ubi9" + distros: + - name: UBI 8 + app_versions: ["3.6", "3.9"] + + - name: UBI 9 + app_versions: ["3.9", "3.12-minimal"] + + - name: UBI 10 + app_versions: ["3.12-minimal"] + + - filename: python-rhel.json + latest: "3.12-ubi8" + distros: + - name: UBI 8 + app_versions: ["3.6", "3.9"] + + - name: UBI 9 + app_versions: ["3.9", "3.12-minimal"] + + - name: UBI 10 + app_versions: ["3.12-minimal"] + + - filename: python-rhel-aarch64.json + latest: "3.12-ubi8" + distros: + - name: UBI 8 + app_versions: ["3.6", "3.9"] + + - name: UBI 9 + app_versions: ["3.9", "3.12-minimal"] + + - name: UBI 10 + app_versions: ["3.12-minimal"] + +... diff --git a/imagestreams/python-centos.json b/imagestreams/python-centos.json index 0f6aeae2..137fc78e 100644 --- a/imagestreams/python-centos.json +++ b/imagestreams/python-centos.json @@ -1,6 +1,6 @@ { "kind": "ImageStream", - "apiVersion": "v1", + "apiVersion": "image.openshift.io/v1", "metadata": { "name": "python", "annotations": { @@ -10,139 +10,133 @@ "spec": { "tags": [ { - "name": "latest", + "name": "3.6-ubi8", "annotations": { - "openshift.io/display-name": "Python (Latest)", + "openshift.io/display-name": "Python 3.6 (UBI 8)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python applications on UBI. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.8/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Python available on OpenShift, including major version updates.", + "description": "Build and run Python 3.6 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.6/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python", + "version": "3.6", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { - "kind": "ImageStreamTag", - "name": "3.8-ubi8" + "kind": "DockerImage", + "name": "registry.access.redhat.com/ubi8/python-36:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "3.8-ubi8", + "name": "3.9-ubi8", "annotations": { - "openshift.io/display-name": "Python 3.8 (UBI 8)", + "openshift.io/display-name": "Python 3.9 (UBI 8)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 3.8 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.8/README.md.", + "description": "Build and run Python 3.9 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.9/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:3.8,python", - "version": "3.8", + "version": "3.9", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.access.redhat.com/ubi8/python-38:latest" + "name": "registry.access.redhat.com/ubi8/python-39:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "3.8-ubi7", + "name": "3.9-ubi9", "annotations": { - "openshift.io/display-name": "Python 3.8 (UBI 7)", + "openshift.io/display-name": "Python 3.9 (UBI 9)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 3.8 applications on UBI 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.8/README.md.", + "description": "Build and run Python 3.9 applications on UBI 9. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.9/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:3.8,python", - "version": "3.8", + "version": "3.9", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.access.redhat.com/ubi7/python-38:latest" + "name": "registry.access.redhat.com/ubi9/python-39:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "3.6-ubi8", + "name": "3.12-ubi9", "annotations": { - "openshift.io/display-name": "Python 3.6 (UBI 8)", + "openshift.io/display-name": "Python 3.12 (UBI 9)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 3.6 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.6/README.md.", + "description": "Build and run Python 3.12 applications on UBI 9. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:3.6,python", - "version": "3.6", + "version": "3.12", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.access.redhat.com/ubi8/python-36:latest" + "name": "registry.access.redhat.com/ubi9/python-312:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "2.7-ubi8", + "name": "3.12-minimal-ubi9", "annotations": { - "openshift.io/display-name": "Python 2.7 (UBI 8)", + "openshift.io/display-name": "Python 3.12-minimal (UBI 9)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 2.7 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/2.7/README.md.", + "description": "Build and run Python 3.12-minimal applications on UBI 9. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12-minimal/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:2.7,python", - "version": "2.7", + "version": "3.12-minimal", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.access.redhat.com/ubi8/python-27:latest" + "name": "registry.access.redhat.com/ubi9/python-312-minimal:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "2.7-ubi7", + "name": "3.12-minimal-ubi10", "annotations": { - "openshift.io/display-name": "Python 2.7 (UBI 7)", + "openshift.io/display-name": "Python 3.12-minimal (UBI 10)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 2.7 applications on UBI 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/2.7/README.md.", + "description": "Build and run Python 3.12-minimal applications on UBI 10. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12-minimal/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:2.7,python", - "version": "2.7", + "version": "3.12-minimal", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.access.redhat.com/ubi7/python-27:latest" + "name": "registry.access.redhat.com/ubi10/python-312-minimal:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "2.7", + "name": "latest", "annotations": { - "openshift.io/display-name": "Python 2.7", + "openshift.io/display-name": "Python 3.12 (Latest)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 2.7 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/2.7/README.md.", + "description": "Build and run Python 3.12 applications on UBI 9. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version available on OpenShift, including major version updates.\n", "iconClass": "icon-python", - "tags": "builder,python,hidden", - "supports":"python:2.7,python", - "version": "2.7", + "tags": "builder,python", + "version": "3.12", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { - "kind": "DockerImage", - "name": "docker.io/centos/python-27-centos7:latest" + "kind": "ImageStreamTag", + "name": "3.12-ubi9" }, "referencePolicy": { "type": "Local" diff --git a/imagestreams/python-rhel-aarch64.json b/imagestreams/python-rhel-aarch64.json index 1c2ed274..111b7444 100644 --- a/imagestreams/python-rhel-aarch64.json +++ b/imagestreams/python-rhel-aarch64.json @@ -1,6 +1,6 @@ { "kind": "ImageStream", - "apiVersion": "v1", + "apiVersion": "image.openshift.io/v1", "metadata": { "name": "python", "annotations": { @@ -10,79 +10,133 @@ "spec": { "tags": [ { - "name": "latest", + "name": "3.6-ubi8", "annotations": { - "openshift.io/display-name": "Python (Latest)", + "openshift.io/display-name": "Python 3.6 (UBI 8)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python applications on UBI. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.8/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Python available on OpenShift, including major version updates.", + "description": "Build and run Python 3.6 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.6/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python", + "version": "3.6", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { - "kind": "ImageStreamTag", - "name": "3.8-ubi8" + "kind": "DockerImage", + "name": "registry.redhat.io/ubi8/python-36:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "3.8-ubi8", + "name": "3.9-ubi8", "annotations": { - "openshift.io/display-name": "Python 3.8 (UBI 8)", + "openshift.io/display-name": "Python 3.9 (UBI 8)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 3.8 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.8/README.md.", + "description": "Build and run Python 3.9 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.9/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:3.8,python", - "version": "3.8", + "version": "3.9", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.redhat.io/ubi8/python-38:latest" + "name": "registry.redhat.io/ubi8/python-39:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "3.6-ubi8", + "name": "3.9-ubi9", "annotations": { - "openshift.io/display-name": "Python 3.6 (UBI 8)", + "openshift.io/display-name": "Python 3.9 (UBI 9)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 3.6 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.6/README.md.", + "description": "Build and run Python 3.9 applications on UBI 9. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.9/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:3.6,python", - "version": "3.6", + "version": "3.9", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.redhat.io/ubi8/python-36:latest" + "name": "registry.redhat.io/ubi9/python-39:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "3.12-ubi9", + "annotations": { + "openshift.io/display-name": "Python 3.12 (UBI 9)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Python 3.12 applications on UBI 9. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12/README.md.", + "iconClass": "icon-python", + "tags": "builder,python", + "version": "3.12", + "sampleRepo": "https://github.com/sclorg/django-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi9/python-312:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "3.12-minimal-ubi9", + "annotations": { + "openshift.io/display-name": "Python 3.12-minimal (UBI 9)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Python 3.12-minimal applications on UBI 9. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12-minimal/README.md.", + "iconClass": "icon-python", + "tags": "builder,python", + "version": "3.12-minimal", + "sampleRepo": "https://github.com/sclorg/django-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi9/python-312-minimal:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "2.7-ubi8", + "name": "3.12-minimal-ubi10", "annotations": { - "openshift.io/display-name": "Python 2.7 (UBI 8)", + "openshift.io/display-name": "Python 3.12-minimal (UBI 10)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 2.7 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/2.7/README.md.", + "description": "Build and run Python 3.12-minimal applications on UBI 10. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12-minimal/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:2.7,python", - "version": "2.7", + "version": "3.12-minimal", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.redhat.io/ubi8/python-27:latest" + "name": "registry.redhat.io/ubi10/python-312-minimal:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "latest", + "annotations": { + "openshift.io/display-name": "Python 3.12 (Latest)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Python 3.12 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version available on OpenShift, including major version updates.\n", + "iconClass": "icon-python", + "tags": "builder,python", + "version": "3.12", + "sampleRepo": "https://github.com/sclorg/django-ex.git" + }, + "from": { + "kind": "ImageStreamTag", + "name": "3.12-ubi8" }, "referencePolicy": { "type": "Local" diff --git a/imagestreams/python-rhel.json b/imagestreams/python-rhel.json index ce2e0807..111b7444 100644 --- a/imagestreams/python-rhel.json +++ b/imagestreams/python-rhel.json @@ -1,6 +1,6 @@ { "kind": "ImageStream", - "apiVersion": "v1", + "apiVersion": "image.openshift.io/v1", "metadata": { "name": "python", "annotations": { @@ -10,159 +10,133 @@ "spec": { "tags": [ { - "name": "latest", - "annotations": { - "openshift.io/display-name": "Python (Latest)", - "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python applications on UBI. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.8/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Python available on OpenShift, including major version updates.", - "iconClass": "icon-python", - "tags": "builder,python", - "supports":"python", - "sampleRepo": "https://github.com/sclorg/django-ex.git" - }, - "from": { - "kind": "ImageStreamTag", - "name": "3.8-ubi8" - }, - "referencePolicy": { - "type": "Local" - } - }, - { - "name": "3.8-ubi8", + "name": "3.6-ubi8", "annotations": { - "openshift.io/display-name": "Python 3.8 (UBI 8)", + "openshift.io/display-name": "Python 3.6 (UBI 8)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 3.8 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.8/README.md.", + "description": "Build and run Python 3.6 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.6/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:3.8,python", - "version": "3.8", + "version": "3.6", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.redhat.io/ubi8/python-38:latest" + "name": "registry.redhat.io/ubi8/python-36:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "3.8-ubi7", + "name": "3.9-ubi8", "annotations": { - "openshift.io/display-name": "Python 3.8 (UBI 7)", + "openshift.io/display-name": "Python 3.9 (UBI 8)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 3.8 applications on UBI 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.8/README.md.", + "description": "Build and run Python 3.9 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.9/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:3.8,python", - "version": "3.8", + "version": "3.9", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.redhat.io/ubi7/python-38:latest" + "name": "registry.redhat.io/ubi8/python-39:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "3.8", + "name": "3.9-ubi9", "annotations": { - "openshift.io/display-name": "Python 3.8", + "openshift.io/display-name": "Python 3.9 (UBI 9)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 3.8 applications on RHEL 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.8/README.md.", + "description": "Build and run Python 3.9 applications on UBI 9. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.9/README.md.", "iconClass": "icon-python", - "tags": "builder,python,hidden", - "supports":"python:3.8,python", - "version": "3.8", + "tags": "builder,python", + "version": "3.9", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.redhat.io/rhscl/python-38-rhel7:latest" + "name": "registry.redhat.io/ubi9/python-39:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "3.6-ubi8", + "name": "3.12-ubi9", "annotations": { - "openshift.io/display-name": "Python 3.6 (UBI 8)", + "openshift.io/display-name": "Python 3.12 (UBI 9)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 3.6 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.6/README.md.", + "description": "Build and run Python 3.12 applications on UBI 9. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:3.6,python", - "version": "3.6", + "version": "3.12", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.redhat.io/ubi8/python-36:latest" + "name": "registry.redhat.io/ubi9/python-312:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "2.7-ubi8", + "name": "3.12-minimal-ubi9", "annotations": { - "openshift.io/display-name": "Python 2.7 (UBI 8)", + "openshift.io/display-name": "Python 3.12-minimal (UBI 9)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 2.7 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/2.7/README.md.", + "description": "Build and run Python 3.12-minimal applications on UBI 9. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12-minimal/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:2.7,python", - "version": "2.7", + "version": "3.12-minimal", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.redhat.io/ubi8/python-27:latest" + "name": "registry.redhat.io/ubi9/python-312-minimal:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "2.7-ubi7", + "name": "3.12-minimal-ubi10", "annotations": { - "openshift.io/display-name": "Python 2.7 (UBI 7)", + "openshift.io/display-name": "Python 3.12-minimal (UBI 10)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 2.7 applications on UBI 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/2.7/README.md.", + "description": "Build and run Python 3.12-minimal applications on UBI 10. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12-minimal/README.md.", "iconClass": "icon-python", "tags": "builder,python", - "supports":"python:2.7,python", - "version": "2.7", + "version": "3.12-minimal", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { "kind": "DockerImage", - "name": "registry.redhat.io/ubi7/python-27:latest" + "name": "registry.redhat.io/ubi10/python-312-minimal:latest" }, "referencePolicy": { "type": "Local" } }, { - "name": "2.7", + "name": "latest", "annotations": { - "openshift.io/display-name": "Python 2.7", + "openshift.io/display-name": "Python 3.12 (Latest)", "openshift.io/provider-display-name": "Red Hat, Inc.", - "description": "Build and run Python 2.7 applications on RHEL 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/2.7/README.md.", + "description": "Build and run Python 3.12 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.12/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version available on OpenShift, including major version updates.\n", "iconClass": "icon-python", - "tags": "builder,python,hidden", - "supports":"python:2.7,python", - "version": "2.7", + "tags": "builder,python", + "version": "3.12", "sampleRepo": "https://github.com/sclorg/django-ex.git" }, "from": { - "kind": "DockerImage", - "name": "registry.redhat.io/rhscl/python-27-rhel7:latest" + "kind": "ImageStreamTag", + "name": "3.12-ubi8" }, "referencePolicy": { "type": "Local" diff --git a/manifest-minimal.yml b/manifest-minimal.yml new file mode 100644 index 00000000..8eec6f5c --- /dev/null +++ b/manifest-minimal.yml @@ -0,0 +1,228 @@ +# Files containing distgen directives +DISTGEN_RULES: + - src: src/README-minimal.md + dest: README.md + + + - src: src/s2i/bin/assemble + dest: s2i/bin/assemble + mode: "0755" + + - src: src/s2i/bin/usage + dest: s2i/bin/usage + mode: "0755" + + - src: src/s2i/bin/run + dest: s2i/bin/run + mode: "0755" + + - src: test/run + dest: test/run + mode: "0755" + + - src: test/run-pytest + dest: test/run-pytest + mode: "0755" + + - src: src/test/pipenv-test-app/Pipfile + dest: test/pipenv-test-app/Pipfile + + - src: src/test/pipenv-test-app/Pipfile.lock + dest: test/pipenv-test-app/Pipfile.lock + + - src: src/test/pipenv-test-app/Pipfile + dest: test/micropipenv-test-app/Pipfile + + - src: src/test/pipenv-test-app/Pipfile.lock + dest: test/micropipenv-test-app/Pipfile.lock + + - src: src/test/from-dockerfile/uwsgi.Dockerfile.tpl + dest: test/from-dockerfile/uwsgi.Dockerfile.tpl + + - src: src/test/pin-pipenv-version-test-app/.s2i/environment + dest: test/pyuwsgi-pipenv-test-app/.s2i/environment + + +# Files containing distgen directives, which are used for each +# (distro, version) combination not excluded in multispec +DISTGEN_MULTI_RULES: + - src: src/Dockerfile-minimal.template + dest: Dockerfile.rhel8 + + - src: src/Dockerfile-minimal.template + dest: Dockerfile.rhel9 + + - src: src/Dockerfile-minimal.template + dest: Dockerfile.rhel10 + + - src: src/Dockerfile-minimal.template + dest: Dockerfile.fedora + + - src: src/Dockerfile-minimal.template + dest: Dockerfile.c9s + + - src: src/Dockerfile-minimal.template + dest: Dockerfile.c10s + +# Files to copy +COPY_RULES: + - src: src/root/opt/app-root/etc/generate_container_user + dest: root/opt/app-root/etc/generate_container_user + + - src: src/s2i/bin/init-wrapper + dest: s2i/bin/init-wrapper + mode: "0755" + + - src: examples/pipenv-test-app/testapp.py + dest: test/pipenv-test-app/testapp.py + + - src: examples/pipenv-test-app/setup.py + dest: test/pipenv-test-app/setup.py + + - src: examples/pipenv-test-app/.s2i/environment + dest: test/pipenv-test-app/.s2i/environment + + - src: examples/pipenv-test-app/.gitignore + dest: test/pipenv-test-app/.gitignore + + - src: examples/micropipenv-test-app/testapp.py + dest: test/micropipenv-test-app/testapp.py + + - src: examples/micropipenv-test-app/setup.py + dest: test/micropipenv-test-app/setup.py + + - src: examples/micropipenv-test-app/.s2i/environment + dest: test/micropipenv-test-app/.s2i/environment + + - src: examples/micropipenv-test-app/.gitignore + dest: test/micropipenv-test-app/.gitignore + + - src: common/shared-scripts/core/usr/bin/cgroup-limits + dest: root/usr/bin/cgroup-limits + mode: "0755" + + - src: common/shared-scripts/core/usr/bin/fix-permissions + dest: root/usr/bin/fix-permissions + mode: "0755" + + - src: common/shared-scripts/core/usr/bin/rpm-file-permissions + dest: root/usr/bin/rpm-file-permissions + mode: "0755" + + - src: examples/pyuwsgi-pipenv-test-app/requirements.txt + dest: test/pyuwsgi-pipenv-test-app/requirements.txt + + - src: examples/pyuwsgi-pipenv-test-app/wsgi.py + dest: test/pyuwsgi-pipenv-test-app/wsgi.py + + - src: examples/pyuwsgi-pipenv-test-app/app.sh + dest: test/pyuwsgi-pipenv-test-app/app.sh + +# Symbolic links +# This section is the last one on purpose because the generator.py +# does not allow dead symlinks. +SYMLINK_RULES: + - src: ../../examples/app-home-test-app + dest: test/app-home-test-app + + - src: ../../examples/app-module-test-app + dest: test/app-module-test-app + + - src: ../../examples/django-test-app + dest: test/django-test-app + + - src: ../../examples/django-different-port-test-app + dest: test/django-different-port-test-app + + - src: ../../examples/gunicorn-different-port-test-app + dest: test/gunicorn-different-port-test-app + + - src: ../../examples/gunicorn-config-different-port-test-app + dest: test/gunicorn-config-different-port-test-app + + - src: ../../examples/gunicorn-python-configfile-different-port-test-app + dest: test/gunicorn-python-configfile-different-port-test-app + + - src: ../../examples/locale-test-app + dest: test/locale-test-app + + - src: ../../examples/micropipenv-requirements-test-app + dest: test/micropipenv-requirements-test-app + + - src: ../../examples/uwsgi-test-app + dest: test/uwsgi-test-app + + - src: ../../examples/numpy-test-app + dest: test/numpy-test-app + + - src: ../../examples/poetry-src-layout-test-app + dest: test/poetry-src-layout-test-app + + - src: ../../examples/setup-requirements-test-app + dest: test/setup-requirements-test-app + + - src: ../../examples/setup-test-app + dest: test/setup-test-app + + - src: ../../examples/setup-cfg-test-app + dest: test/setup-cfg-test-app + + - src: ../../examples/standalone-test-app + dest: test/standalone-test-app + + - src: ../../examples/standalone-custom-pypi-index-test-app + dest: test/standalone-custom-pypi-index-test-app + + - src: ../../src/test/pipenv-and-micropipenv-should-fail-test-app + dest: test/pipenv-and-micropipenv-should-fail-test-app + + - src: ../../../src/test/from-dockerfile/mod_wsgi.Dockerfile.tpl + dest: test/from-dockerfile/mod_wsgi.Dockerfile.tpl + + - src: ../../test/run-openshift-pytest + dest: test/run-openshift-pytest + + - src: ../../test/test_container_application.py + dest: test/test_container_application.py + + - src: ../../test/test_container_basics.py + dest: test/test_container_basics.py + + - src: ../../test/test_ocp_deploy_templates.py + dest: test/test_ocp_deploy_templates.py + + - src: ../../test/test_ocp_imagestreams_quickstart.py + dest: test/test_ocp_imagestreams_quickstart.py + + - src: ../../test/test_ocp_python_ex_standalone.py + dest: test/test_ocp_python_ex_standalone.py + + - src: ../../test/test_ocp_python_ex_template.py + dest: test/test_ocp_python_ex_template.py + + - src: ../../test/test_ocp_helm_python_django_app.py + dest: test/test_ocp_helm_python_django_app.py + + - src: ../../test/test_ocp_helm_python_django_psql_persistent.py + dest: test/test_ocp_helm_python_django_psql_persistent.py + + - src: ../../test/test_ocp_helm_python_imagestreams.py + dest: test/test_ocp_helm_python_imagestreams.py + + - src: ../../test/conftest.py + dest: test/conftest.py + + - src: ../../test/__init__.py + dest: test/__init__.py + + - src: ../../common/test-lib.sh + dest: test/test-lib.sh + + - src: ../../imagestreams + dest: test/imagestreams + + - src: ../../common/check_imagestreams.py + dest: test/check_imagestreams.py + + - src: ../../../src/root/opt/wheels/ + dest: root/opt/wheels diff --git a/manifest.sh b/manifest.sh deleted file mode 100644 index cda96046..00000000 --- a/manifest.sh +++ /dev/null @@ -1,149 +0,0 @@ -# Manifest for Dockerfiles creation -# every dest path will be prefixed by $DESTDIR/$version - -# Files containing distgen directives -DISTGEN_RULES=" - src=src/cccp.yml - dest=cccp.yml; - - src=src/README.md - dest=README.md; - - src=src/root/opt/app-root/etc/scl_enable - dest=root/opt/app-root/etc/scl_enable; - - src=src/s2i/bin/assemble - dest=s2i/bin/assemble - mode=0755; - - src=src/s2i/bin/usage - dest=s2i/bin/usage - mode=0755; - - src=test/run - dest=test/run - mode=0755; - - src=src/test/pipenv-test-app/Pipfile - dest=test/pipenv-test-app/Pipfile; - - src=src/test/pipenv-test-app/Pipfile.lock - dest=test/pipenv-test-app/Pipfile.lock; - - src=src/test/pipenv-test-app/Pipfile - dest=test/micropipenv-test-app/Pipfile; - - src=src/test/pipenv-test-app/Pipfile.lock - dest=test/micropipenv-test-app/Pipfile.lock; -" - -# Files containing distgen directives, which are used for each -# (distro, version) combination not excluded in multispec -DISTGEN_MULTI_RULES=" - src=src/Dockerfile.template - dest=Dockerfile; - - src=src/Dockerfile.template - dest=Dockerfile.rhel7; - - src=src/Dockerfile.template - dest=Dockerfile.rhel8; - - src=src/Dockerfile.template - dest=Dockerfile.fedora; -" - -# Symbolic links -SYMLINK_RULES=" - link_target=../../examples/app-home-test-app - link_name=test/app-home-test-app; - - link_target=../../examples/django-test-app - link_name=test/django-test-app; - - link_target=../../examples/locale-test-app - link_name=test/locale-test-app; - - link_target=../../examples/micropipenv-requirements-test-app - link_name=test/micropipenv-requirements-test-app; - - link_target=../../examples/mod-wsgi-test-app - link_name=test/mod-wsgi-test-app; - - link_target=../../examples/npm-virtualenv-uwsgi-test-app - link_name=test/npm-virtualenv-uwsgi-test-app; - - link_target=../../examples/numpy-test-app - link_name=test/numpy-test-app; - - link_target=../../examples/pin-pipenv-version-test-app - link_name=test/pin-pipenv-version-test-app; - - link_target=../../examples/setup-requirements-test-app - link_name=test/setup-requirements-test-app; - - link_target=../../examples/setup-test-app - link_name=test/setup-test-app; - - link_target=../../examples/standalone-test-app - link_name=test/standalone-test-app; - - link_target=../../src/test/pipenv-and-micropipenv-should-fail-test-app - link_name=test/pipenv-and-micropipenv-should-fail-test-app; - - link_target=../../test/run-openshift - link_name=test/run-openshift; - - link_target=../../common/test-lib.sh - link_name=test/test-lib.sh; - - link_target=../../common/test-lib-openshift.sh - link_name=test/test-lib-openshift.sh; - - link_target=../../test/test-lib-python.sh - link_name=test/test-lib-python.sh; - - link_target=../../imagestreams - link_name=test/imagestreams; - - link_target=../../common/check_imagestreams.py - link_name=test/check_imagestreams.py; -" - -# Files to copy -COPY_RULES=" - src=src/root/opt/app-root/etc/generate_container_user - dest=root/opt/app-root/etc/generate_container_user; - - src=src/s2i/bin/run - dest=s2i/bin/run - mode=0755; - - src=src/s2i/bin/init-wrapper - dest=s2i/bin/init-wrapper - mode=0755; - - src=examples/pipenv-test-app/testapp.py - dest=test/pipenv-test-app/testapp.py; - - src=examples/pipenv-test-app/setup.py - dest=test/pipenv-test-app/setup.py; - - src=examples/pipenv-test-app/.s2i/environment - dest=test/pipenv-test-app/.s2i/environment; - - src=examples/pipenv-test-app/.gitignore - dest=test/pipenv-test-app/.gitignore; - - src=examples/micropipenv-test-app/testapp.py - dest=test/micropipenv-test-app/testapp.py; - - src=examples/micropipenv-test-app/setup.py - dest=test/micropipenv-test-app/setup.py; - - src=examples/micropipenv-test-app/.s2i/environment - dest=test/micropipenv-test-app/.s2i/environment; - - src=examples/micropipenv-test-app/.gitignore - dest=test/micropipenv-test-app/.gitignore; -" diff --git a/manifest.yml b/manifest.yml new file mode 100644 index 00000000..4cb6b4de --- /dev/null +++ b/manifest.yml @@ -0,0 +1,227 @@ +# Files containing distgen directives +DISTGEN_RULES: + - src: src/README.md + dest: README.md + + + - src: src/s2i/bin/assemble + dest: s2i/bin/assemble + mode: "0755" + + - src: src/s2i/bin/usage + dest: s2i/bin/usage + mode: "0755" + + - src: src/s2i/bin/run + dest: s2i/bin/run + mode: "0755" + + - src: test/run + dest: test/run + mode: "0755" + + - src: test/run-pytest + dest: test/run-pytest + mode: "0755" + + - src: src/test/pipenv-test-app/Pipfile + dest: test/pipenv-test-app/Pipfile + + - src: src/test/pipenv-test-app/Pipfile.lock + dest: test/pipenv-test-app/Pipfile.lock + + - src: src/test/pipenv-test-app/Pipfile + dest: test/micropipenv-test-app/Pipfile + + - src: src/test/pipenv-test-app/Pipfile.lock + dest: test/micropipenv-test-app/Pipfile.lock + + - src: src/test/from-dockerfile/Dockerfile_no_s2i.tpl + dest: test/from-dockerfile/Dockerfile_no_s2i.tpl + + - src: src/test/pin-pipenv-version-test-app/.s2i/environment + dest: test/pin-pipenv-version-test-app/.s2i/environment + + - src: src/test/pin-pipenv-version-test-app/.s2i/environment + dest: test/pyuwsgi-pipenv-test-app/.s2i/environment + +# Files containing distgen directives, which are used for each +# (distro, version) combination not excluded in multispec +DISTGEN_MULTI_RULES: + - src: src/Dockerfile.template + dest: Dockerfile.rhel8 + + - src: src/Dockerfile.template + dest: Dockerfile.rhel9 + + - src: src/Dockerfile.template + dest: Dockerfile.fedora + + - src: src/Dockerfile.template + dest: Dockerfile.c9s + + - src: src/Dockerfile.template + dest: Dockerfile.c10s + +# Files to copy +COPY_RULES: + - src: src/root/opt/app-root/etc/generate_container_user + dest: root/opt/app-root/etc/generate_container_user + + - src: src/s2i/bin/init-wrapper + dest: s2i/bin/init-wrapper + mode: "0755" + + - src: examples/pipenv-test-app/testapp.py + dest: test/pipenv-test-app/testapp.py + + - src: examples/pipenv-test-app/setup.py + dest: test/pipenv-test-app/setup.py + + - src: examples/pipenv-test-app/.s2i/environment + dest: test/pipenv-test-app/.s2i/environment + + - src: examples/pipenv-test-app/.gitignore + dest: test/pipenv-test-app/.gitignore + + - src: examples/micropipenv-test-app/testapp.py + dest: test/micropipenv-test-app/testapp.py + + - src: examples/micropipenv-test-app/setup.py + dest: test/micropipenv-test-app/setup.py + + - src: examples/micropipenv-test-app/.s2i/environment + dest: test/micropipenv-test-app/.s2i/environment + + - src: examples/micropipenv-test-app/.gitignore + dest: test/micropipenv-test-app/.gitignore + + - src: examples/pin-pipenv-version-test-app/requirements.txt + dest: test/pin-pipenv-version-test-app/requirements.txt + + - src: examples/pin-pipenv-version-test-app/wsgi.py + dest: test/pin-pipenv-version-test-app/wsgi.py + + - src: examples/pin-pipenv-version-test-app/app.sh + dest: test/pin-pipenv-version-test-app/app.sh + + - src: examples/pyuwsgi-pipenv-test-app/requirements.txt + dest: test/pyuwsgi-pipenv-test-app/requirements.txt + + - src: examples/pyuwsgi-pipenv-test-app/wsgi.py + dest: test/pyuwsgi-pipenv-test-app/wsgi.py + + - src: examples/pyuwsgi-pipenv-test-app/app.sh + dest: test/pyuwsgi-pipenv-test-app/app.sh + +# Symbolic links +# This section is the last one on purpose because the generator.py +# does not allow dead symlinks. +SYMLINK_RULES: + - src: ../../examples/app-home-test-app + dest: test/app-home-test-app + + - src: ../../examples/app-module-test-app + dest: test/app-module-test-app + + - src: ../../examples/django-test-app + dest: test/django-test-app + + - src: ../../examples/django-different-port-test-app + dest: test/django-different-port-test-app + + - src: ../../examples/gunicorn-different-port-test-app + dest: test/gunicorn-different-port-test-app + + - src: ../../examples/gunicorn-config-different-port-test-app + dest: test/gunicorn-config-different-port-test-app + + - src: ../../examples/gunicorn-python-configfile-different-port-test-app + dest: test/gunicorn-python-configfile-different-port-test-app + + - src: ../../examples/locale-test-app + dest: test/locale-test-app + + - src: ../../examples/micropipenv-requirements-test-app + dest: test/micropipenv-requirements-test-app + + - src: ../../examples/mod-wsgi-test-app + dest: test/mod-wsgi-test-app + + - src: ../../examples/npm-virtualenv-uwsgi-test-app + dest: test/npm-virtualenv-uwsgi-test-app + + - src: ../../examples/numpy-test-app + dest: test/numpy-test-app + + - src: ../../examples/poetry-src-layout-test-app + dest: test/poetry-src-layout-test-app + + - src: ../../examples/setup-requirements-test-app + dest: test/setup-requirements-test-app + + - src: ../../examples/setup-test-app + dest: test/setup-test-app + + - src: ../../examples/setup-cfg-test-app + dest: test/setup-cfg-test-app + + - src: ../../examples/standalone-test-app + dest: test/standalone-test-app + + - src: ../../examples/standalone-custom-pypi-index-test-app + dest: test/standalone-custom-pypi-index-test-app + + - src: ../../src/test/pipenv-and-micropipenv-should-fail-test-app + dest: test/pipenv-and-micropipenv-should-fail-test-app + + - src: ../../../src/test/from-dockerfile/Dockerfile.tpl + dest: test/from-dockerfile/Dockerfile.tpl + + - src: ../../test/run-openshift-pytest + dest: test/run-openshift-pytest + + - src: ../../test/test_container_application.py + dest: test/test_container_application.py + + - src: ../../test/test_container_basics.py + dest: test/test_container_basics.py + + - src: ../../test/test_ocp_deploy_templates.py + dest: test/test_ocp_deploy_templates.py + + - src: ../../test/test_ocp_imagestreams_quickstart.py + dest: test/test_ocp_imagestreams_quickstart.py + + - src: ../../test/test_ocp_python_ex_standalone.py + dest: test/test_ocp_python_ex_standalone.py + + - src: ../../test/test_ocp_python_ex_template.py + dest: test/test_ocp_python_ex_template.py + + - src: ../../test/test_ocp_helm_python_django_app.py + dest: test/test_ocp_helm_python_django_app.py + + - src: ../../test/test_ocp_helm_python_django_psql_persistent.py + dest: test/test_ocp_helm_python_django_psql_persistent.py + + - src: ../../test/test_ocp_helm_python_imagestreams.py + dest: test/test_ocp_helm_python_imagestreams.py + + - src: ../../test/conftest.py + dest: test/conftest.py + + - src: ../../test/__init__.py + dest: test/__init__.py + + - src: ../../common/test-lib.sh + dest: test/test-lib.sh + + - src: ../../imagestreams + dest: test/imagestreams + + - src: ../../common/check_imagestreams.py + dest: test/check_imagestreams.py + + - src: ../../../src/root/opt/wheels/ + dest: root/opt/wheels diff --git a/specs/multispec.yml b/specs/multispec.yml index 1649ec71..2486f3e0 100644 --- a/specs/multispec.yml +++ b/specs/multispec.yml @@ -2,163 +2,270 @@ version: 1 specs: distroinfo: - centos7: - distros: - - centos-7-x86_64 - el_version: "7" - s2i_base: centos/s2i-base-centos7 - org: "centos" - prod: "centos7" - logos: "centos-logos" - enablerepo: "--enablerepo=centosplus" - preinstall_cmd: ['yum install -y centos-release-scl'] - python_pkgs: ['{{ spec.scl }}', '{{ spec.scl }}-python-devel', - '{{ spec.scl }}-python-setuptools', '{{ spec.scl }}-python-pip'] - base_pkgs: ['nss_wrapper', 'httpd24', 'httpd24-httpd-devel', 'httpd24-mod_ssl', - 'httpd24-mod_auth_kerb', 'httpd24-mod_ldap', 'httpd24-mod_session', - 'atlas-devel', 'gcc-gfortran', 'libffi-devel', 'libtool-ltdl enchant'] - extra_pkgs: - "2.7": ['libjpeg-turbo', 'libjpeg-turbo-devel'] - - rhel7: - distros: - - rhel-7-x86_64 - el_version: "7" - s2i_base: rhscl/s2i-base-rhel7 - org: "rhscl" - prod: "rhel7" - logos: "redhat-logos" - python3_component_prefix: "rh-" - preinstall_cmd: ['yum install -y yum-utils', - 'prepare-yum-repositories rhel-server-rhscl-7-rpms'] - python_pkgs: ['{{ spec.scl }}', '{{ spec.scl }}-python-devel', - '{{ spec.scl }}-python-setuptools', '{{ spec.scl }}-python-pip'] - base_pkgs: ['nss_wrapper', 'httpd24', 'httpd24-httpd-devel', 'httpd24-mod_ssl', - 'httpd24-mod_auth_kerb', 'httpd24-mod_ldap', 'httpd24-mod_session', - 'atlas-devel', 'gcc-gfortran', 'libffi-devel', 'libtool-ltdl enchant'] - ubi_versions: ['2.7', '3.6', '3.8'] - rhel8: distros: - rhel-8-x86_64 el_version: "8" + minimal_image: "ubi8/ubi-minimal:latest" s2i_base: ubi8/s2i-base img_tag: "1" org: "ubi8" prod: "rhel8" logos: "redhat-logos-httpd" preinstall_cmd: ['yum -y module enable {{ spec.module_stream }} httpd:2.4'] - python_pkgs: ['{{ spec.pkg_prefix }}-setuptools', '{{ spec.pkg_prefix }}-pip'] - base_pkgs: ['nss_wrapper', 'httpd', 'httpd-devel', 'mod_ssl', + python_pkgs: [] + base_pkgs: ['nss_wrapper-libs', 'httpd', 'httpd-devel', 'mod_ssl', 'mod_auth_gssapi', 'mod_ldap', 'mod_session', - 'atlas-devel', 'gcc-gfortran', 'libffi-devel', 'libtool-ltdl enchant'] + 'atlas-devel', 'gcc-gfortran', 'libffi-devel', 'libtool-ltdl', 'enchant', + 'krb5-devel'] extra_pkgs: - "2.7": ['{{ spec.pkg_prefix }}', '{{ spec.pkg_prefix }}-devel', 'glibc-langpack-en', - '{{ spec.pkg_prefix }}-virtualenv'] - "3.6": ['python36', 'python36-devel', '{{ spec.pkg_prefix }}-virtualenv'] - "3.8": ['{{ spec.pkg_prefix }}', '{{ spec.pkg_prefix }}-devel'] - ubi_versions: ['2.7', '3.6', '3.8'] + "3.6": ['python36', 'python36-devel', '{{ spec.pkg_prefix }}-virtualenv', + '{{ spec.pkg_prefix }}-setuptools', '{{ spec.pkg_prefix }}-pip'] + "default": ['{{ spec.pkg_prefix }}', '{{ spec.pkg_prefix }}-devel', + '{{ spec.pkg_prefix }}-setuptools', '{{ spec.pkg_prefix }}-pip'] + "3.12": ['{{ spec.pkg_prefix }}', '{{ spec.pkg_prefix }}-devel', + '{{ spec.pkg_prefix }}-setuptools', '{{ spec.pkg_prefix }}-pip'] + ubi_versions: ['3.6', '3.9', '3.12'] - fedora31: + rhel9: distros: - - fedora-31-x86_64 - fedora_version: "31" - s2i_base: registry.fedoraproject.org/f31/s2i-base - img_tag: "latest" - python_pkgs: ['python3', 'python3-devel', 'python3-setuptools', 'python3-pip'] - base_pkgs: ['nss_wrapper', 'httpd', 'httpd-devel', 'atlas-devel', 'gcc-gfortran', - 'libffi-devel', 'libtool-ltdl enchant', 'redhat-rpm-config'] + - rhel-9-x86_64 + el_version: "9" + minimal_image: "ubi9/ubi-minimal:latest" + s2i_base: ubi9/s2i-base + img_tag: "1" + org: "ubi9" + prod: "rhel9" + logos: "redhat-logos-httpd" + python_pkgs: [] + base_pkgs: ['nss_wrapper-libs', 'httpd', 'httpd-devel', 'mod_ssl', + 'mod_auth_gssapi', 'mod_ldap', 'mod_session', + 'atlas-devel', 'gcc-gfortran', 'libffi-devel', 'libtool-ltdl', 'enchant', + 'krb5-devel'] + ubi_versions: ['3.9', '3.12', '3.14'] + extra_pkgs: + "3.9": ['python3', 'python3-devel', 'python3-setuptools', 'python3-pip'] + "3.12": ['python3.12', 'python3.12-devel', 'python3.12-pip'] + "3.14": ['python3.14', 'python3.14-devel', 'python3.14-pip'] - fedora32: + rhel10: distros: - - fedora-32-x86_64 - fedora_version: "32" - s2i_base: registry.fedoraproject.org/f32/s2i-base - img_tag: "latest" + - rhel-10-x86_64 + el_version: "10" + minimal_image: "ubi10-minimal:latest" + s2i_base: ubi10/s2i-base + img_tag: "1" + org: "ubi10" + prod: "rhel10" + logos: "redhat-logos-httpd" + python_pkgs: [] + base_pkgs: ['nss_wrapper-libs', 'httpd', 'httpd-devel', 'mod_ssl', + 'mod_auth_gssapi', 'mod_ldap', 'mod_session', + 'atlas-devel', 'gcc-gfortran', 'libffi-devel', 'libtool-ltdl', 'enchant', + 'krb5-devel'] + ubi_versions: ['3.12', '3.14'] + extra_pkgs: + "3.12": ['python3.12', 'python3.12-devel', 'python3.12-pip'] + "3.14": ['python3.14', 'python3.14-devel', 'python3.14-pip'] + + + c9s: + distros: + - centos-stream-9-x86_64 + el_version: "9" + minimal_image: "quay.io/centos/centos:stream9-minimal" + s2i_base: quay.io/sclorg/s2i-base-c9s + img_tag: "c9s" + org: "sclorg" + prod: "c9s" + python_pkgs: [] + base_pkgs: ['nss_wrapper-libs', 'httpd', 'httpd-devel', 'mod_ssl', + 'mod_auth_gssapi', 'mod_ldap', 'mod_session', + 'atlas-devel', 'gcc-gfortran', 'libffi-devel', 'libtool-ltdl', 'enchant', + 'krb5-devel'] + extra_pkgs: + "3.9": ['python3', 'python3-devel', 'python3-setuptools', 'python3-pip'] + "3.12": ['python3.12', 'python3.12-devel', 'python3.12-pip'] + "3.14": ['python3.14', 'python3.14-devel', 'python3.14-pip'] + + c10s: + distros: + - centos-stream-10-x86_64 + el_version: "10" + minimal_image: "quay.io/centos/centos:stream10-minimal" + s2i_base: quay.io/sclorg/s2i-base-c10s + img_tag: "c10s" + org: "sclorg" + prod: "c10s" + python_pkgs: [] + base_pkgs: ['nss_wrapper-libs', 'httpd', 'httpd-devel', 'mod_ssl', + 'mod_auth_gssapi', 'mod_ldap', 'mod_session', + 'gcc-gfortran', 'libffi-devel', 'libtool-ltdl', + 'krb5-devel'] + enable_epel_for: ["3.13", "3.13-minimal"] + extra_pkgs: + "3.12": ['python3', 'python3-devel', 'python3-setuptools', 'python3-pip'] + "3.13": ['python3.13', 'python3.13-devel', 'python3.13-pip'] + "3.14": ['python3.14', 'python3.14-devel', 'python3.14-pip'] + + fedora42: + distros: + - fedora-42-x86_64 + fedora_version: "42" + minimal_image: "quay.io/fedora/fedora-minimal:42" + s2i_base: quay.io/fedora/s2i-base + img_tag: "42" + org: "fedora" python_pkgs: ['python3', 'python3-devel', 'python3-setuptools', 'python3-pip'] - base_pkgs: ['nss_wrapper', 'httpd', 'httpd-devel', 'atlas-devel', 'gcc-gfortran', - 'libffi-devel', 'libtool-ltdl enchant', 'redhat-rpm-config'] + base_pkgs: ['nss_wrapper-libs', 'httpd', 'httpd-devel', 'atlas-devel', 'gcc-gfortran', + 'libffi-devel', 'libtool-ltdl', 'enchant', 'redhat-rpm-config', + 'krb5-devel'] - fedora33: + fedora43: distros: - - fedora-33-x86_64 - fedora_version: "33" - s2i_base: registry.fedoraproject.org/f33/s2i-base - img_tag: "latest" + - fedora-43-x86_64 + fedora_version: "43" + minimal_image: "quay.io/fedora/fedora-minimal:43" + s2i_base: quay.io/fedora/s2i-base + img_tag: "43" + org: "fedora" python_pkgs: ['python3', 'python3-devel', 'python3-setuptools', 'python3-pip'] - base_pkgs: ['nss_wrapper', 'httpd', 'httpd-devel', 'atlas-devel', 'gcc-gfortran', - 'libffi-devel', 'libtool-ltdl enchant', 'redhat-rpm-config'] + base_pkgs: ['nss_wrapper-libs', 'httpd', 'httpd-devel', 'atlas-devel', 'gcc-gfortran', + 'libffi-devel', 'libtool-ltdl', 'enchant', 'redhat-rpm-config', + 'krb5-devel'] version: + "3.14-minimal": + version: "3.14" + short_ver: "314" + minimal: true + base_img_version: "1" + python_img_version: "1" + # Python 3.14 doesn't come as a module + module_stream: "" + pkg_prefix: "python3.14" + main_image: "quay.io/fedora/python-314-minimal" + + "3.14": + version: "3.14" + short_ver: "314" + minimal: false + base_img_version: "1" + # Python 3.14 doesn't come as a module + module_stream: "" + pkg_prefix: "python3.14" + main_image: "quay.io/fedora/python-314" + + "3.13-minimal": + version: "3.13" + short_ver: "313" + minimal: true + base_img_version: "1" + python_img_version: "1" + # Python 3.13 doesn't come as a module + module_stream: "" + pkg_prefix: "python3.13" + main_image: "quay.io/fedora/python-313-minimal" + + "3.13": + version: "3.13" + short_ver: "313" + minimal: false + base_img_version: "1" + # Python 3.13 doesn't come as a module + module_stream: "" + pkg_prefix: "python3.13" + main_image: "quay.io/fedora/python-313" + + "3.12-minimal": + version: "3.12" + short_ver: "312" + minimal: true + base_img_version: "1" + # Python 3.12 doesn't come as a module + module_stream: "" + pkg_prefix: "python3.12" + main_image: "quay.io/sclorg/python-312-minimal-c9s" + + "3.12": + version: "3.12" + short_ver: "312" + minimal: false + base_img_version: "1" + # Python 3.12 doesn't come as a module + module_stream: "" + pkg_prefix: "python3.12" + main_image: "registry.access.redhat.com/ubi9/python-312" + "3.9": version: "3.9" short_ver: "39" + minimal: false base_img_version: "1" - python_img_version: "1" - scl: "rh-python39" module_stream: "python39:3.9" pkg_prefix: "python39" + main_image: "registry.access.redhat.com/ubi9/python-39" - "3.8": - version: "3.8" - short_ver: "38" - base_img_version: "1" - python_img_version: "1" - scl: "rh-python38" - module_stream: "python38:3.8" - pkg_prefix: "python38" - - "3.7": - version: "3.7" - short_ver: "37" + "3.9-minimal": + version: "3.9" + short_ver: "39" + minimal: true base_img_version: "1" - python_img_version: "1" - scl: "rh-python37" - pkg_prefix: "python3" + module_stream: "python39:3.9" + pkg_prefix: "python39" + main_image: "quay.io/sclorg/python-39-minimal-c9s" "3.6": version: "3.6" short_ver: "36" + minimal: false base_img_version: "1" - python_img_version: "1" - scl: "rh-python36" module_stream: "python36:3.6" pkg_prefix: "python3" - - "2.7": - version: "2.7" - short_ver: "27" - scl: "python27" - module_stream: "python27:2.7" - pkg_prefix: "python2" + main_image: "registry.access.redhat.com/ubi8/python-36" matrix: - exclude: + include: - distros: - - fedora-31-x86_64 - - fedora-32-x86_64 - - fedora-33-x86_64 - version: "2.7" - - distros: - - fedora-31-x86_64 - - fedora-32-x86_64 - - fedora-33-x86_64 + - rhel-8-x86_64 version: "3.6" - distros: - - centos-7-x86_64 - - fedora-32-x86_64 - - fedora-33-x86_64 - - rhel-7-x86_64 - - rhel-8-x86_64 - version: "3.7" + - centos-stream-9-x86_64 + - rhel-9-x86_64 + version: "3.9" - distros: - - fedora-31-x86_64 - - fedora-33-x86_64 - version: "3.8" + - centos-stream-9-x86_64 + version: "3.9-minimal" - distros: - - centos-7-x86_64 - - fedora-31-x86_64 - - fedora-32-x86_64 - - rhel-7-x86_64 + - centos-stream-10-x86_64 + - centos-stream-9-x86_64 + - rhel-9-x86_64 - rhel-8-x86_64 - version: "3.9" + version: "3.12" + - distros: + - centos-stream-10-x86_64 + - centos-stream-9-x86_64 + - rhel-8-x86_64 + - rhel-9-x86_64 + - rhel-10-x86_64 + version: "3.12-minimal" + - distros: + - fedora-42-x86_64 + - centos-stream-10-x86_64 + version: "3.13" + - distros: + - fedora-42-x86_64 + - centos-stream-10-x86_64 + version: "3.13-minimal" + - distros: + - fedora-43-x86_64 + - centos-stream-9-x86_64 + - rhel-9-x86_64 + version: "3.14" + - distros: + - fedora-43-x86_64 + - centos-stream-10-x86_64 + - centos-stream-9-x86_64 + - rhel-9-x86_64 + - rhel-10-x86_64 + version: "3.14-minimal" diff --git a/src/Dockerfile-minimal.template b/src/Dockerfile-minimal.template new file mode 100644 index 00000000..26ef35a2 --- /dev/null +++ b/src/Dockerfile-minimal.template @@ -0,0 +1,109 @@ +{% import "src/common.tpl" as common with context %} +{% import "src/" + config.os.id + "/macros.tpl" as macros with context %} +FROM {{ spec.minimal_image }} + + +EXPOSE 8080 + +ENV PYTHON_VERSION={{ spec.version }} \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi{{ spec.el_version }}-python-{{ spec.short_ver }} \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el{{ spec.el_version }}" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as 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 {{ spec.version }}" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python{{ spec.short_ver }},python-{{ spec.short_ver }},rh-python{{ spec.short_ver }}" \ + com.redhat.component="python-{{ spec.short_ver }}-container" \ + name="{{ spec.org }}/python-{{ spec.short_ver }}-minimal{% if spec.org == "sclorg" %}-{{ spec.prod }}{% endif %}" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir={{ spec.version }}-minimal/test/setup-test-app/ ubi{{ spec.el_version }}/python-{{ spec.short_ver }}-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi{{ spec.el_version }}-python-{{ spec.short_ver }}-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="{{ spec.pkg_prefix }} glibc-langpack-en findutils nss_wrapper-libs" && \ + {% if spec.enable_epel_for and spec.version in spec.enable_epel_for %} + microdnf -y install --setopt=install_weak_deps=0 epel-release && \ + {% endif %} + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY {{ spec.version }}-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY {{ spec.version }}-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY {{ spec.version }}-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - 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 \ + python{{ spec.version }} -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/src/Dockerfile.template b/src/Dockerfile.template index 593fb2ca..5c246c6e 100644 --- a/src/Dockerfile.template +++ b/src/Dockerfile.template @@ -7,7 +7,7 @@ {% endif %} # This image provides a Python {{ spec.version }} environment you can use to run your Python # applications. -FROM {{ spec.s2i_base}}{% if img_tag %}:{{ img_tag }}{% endif %} +FROM {{ spec.s2i_base }}{% if img_tag %}:{{ img_tag }}{% endif %} EXPOSE 8080 @@ -18,17 +18,12 @@ ENV PYTHON_VERSION={{ spec.version }} \ PYTHONIOENCODING=UTF-8 \ LC_ALL=en_US.UTF-8 \ LANG=en_US.UTF-8 \ - PIP_NO_CACHE_DIR=off - -{% if spec.el_version == '8' %} -# RHEL7 base images atomatically set these envvars to run scl_enable. RHEl8 -# images, however, don't as most images don't need SCLs any more. But we want -# to run it even on RHEL8, because we set the virtualenv environment as part of -# that script -ENV BASH_ENV=${APP_ROOT}/etc/scl_enable \ - ENV=${APP_ROOT}/etc/scl_enable \ - PROMPT_COMMAND=". ${APP_ROOT}/etc/scl_enable" +{% if spec.version in spec.get("ubi_versions", []) %} + CNB_STACK_ID=com.redhat.stacks.ubi{{ spec.el_version }}-python-{{ spec.short_ver }} \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ {% endif %} + PIP_NO_CACHE_DIR=off {{ macros.env_metadata() -}} ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ @@ -47,10 +42,20 @@ LABEL summary="$SUMMARY" \ io.openshift.expose-services="8080:http" \ io.openshift.tags="builder,python,python{{ spec.short_ver }},python-{{ spec.short_ver }},rh-python{{ spec.short_ver }}" \ {{ macros.labels(spec) }} -{% set extra_pkgs = spec.extra_pkgs[spec.version] if spec.extra_pkgs and spec.extra_pkgs[spec.version] else [] %} +{# Use extra_pkgs for specific Python version or the default or empty list #} +{% if spec.extra_pkgs and spec.version in spec.extra_pkgs %} + {% set extra_pkgs = spec.extra_pkgs[spec.version] %} +{% elif spec.extra_pkgs and "default" in spec.extra_pkgs %} + {% set extra_pkgs = spec.extra_pkgs["default"] %} +{% else %} + {% set extra_pkgs = [] %} +{% endif %} RUN INSTALL_PKGS="{{ common.list_pkgs(extra_pkgs + spec.python_pkgs + spec.base_pkgs) -}} {% if spec.preinstall_cmd %} {{ common.preinstall_cmd(spec) -}} + {% endif %} + {% if spec.enable_epel_for and spec.version in spec.enable_epel_for %} + yum -y install --setopt=install_weak_deps=0 epel-release && \ {% endif %} {{ commands.pkginstaller.install([], {'docs': False}) }}{{ common.enablerepo(spec) }} $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ @@ -61,18 +66,26 @@ RUN INSTALL_PKGS="{{ common.list_pkgs(extra_pkgs + spec.python_pkgs + spec.base_ {{ commands.pkginstaller.cleancache() }} # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. -COPY ./s2i/bin/ $STI_SCRIPTS_PATH +COPY {{ spec.version }}/s2i/bin/ $STI_SCRIPTS_PATH # Copy extra files to the image. -COPY ./root/ / +COPY {{ spec.version }}/root/ / +{% if spec.version != "3.6" %} +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY {{ spec.version }}/root/opt/wheels /opt/wheels +{% endif %} # - Create a Python virtual environment for use by any application to avoid # potential conflicts with Python packages preinstalled in the main Python # installation. # - 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. -{{ macros.permissions_setup(spec) }} +{{ macros.venv_setup(spec) }} USER 1001 # Set the default CMD to print the usage of the language image. diff --git a/src/README-minimal.md b/src/README-minimal.md new file mode 100644 index 00000000..0b4f3d19 --- /dev/null +++ b/src/README-minimal.md @@ -0,0 +1,126 @@ +Python {{ spec.version }} container image - minimal version +============================================ + +This container image is a special version of the [full Python {{ spec.version }} container image](https://github.com/sclorg/s2i-python-container/tree/master/{{ spec.version }}) +provided as a [S2I](https://github.com/openshift/source-to-image) base image for your Python {{ spec.version }} applications. + +Because the minimal and full images work similarly, we document here only the differences and limitations +of the minimal container image. For the documentation of common features see the [full container image docs](https://github.com/sclorg/s2i-python-container/tree/master/{{ spec.version }}). + +The Python {{ spec.version }} minimal container image is currently considered a tech-preview and only available on quay.io. +The image is built on top of the {% if spec.version == 3.8 %}[Red Hat Universal Base Image 8 Micro image](https://catalog.redhat.com/software/containers/ubi8-micro/601a84aadd19c7786c47c8ea) +and therefore uses the RPM packages from Red Hat Enterprise Linux, the same that are used by Python 3.9 supported container image and are part of the [UBI registry](https://www.redhat.com/en/blog/introducing-red-hat-universal-base-image){% else %}[official CentOS Stream base containers](quay.io/centos/centos){% endif %}. + +To pull the Python {{ spec.version }} minimal container image to build on, run + +``` +podman pull {{ spec.main_image }} +``` + +Description +----------- + +The full container image is a universal base image to build your containerized applications on top of. However, its universal nature +means that the resulting containers it produces consume a lot of disk space. This is caused mainly by the fact that the image contains +npm, compilers, header files and some other packages one might need to install and deploy their applications. + +Because size does matter for us and our customers, we have prepared this minimal container image with very limited subset +of installed packages. There are no compilers, no header files, no npm etc and the yum package manager is replaced with a minimalistic +reimplementation called microdnf, making the resulting container images much smaller. This creates some limitations +but we provide ways to workaround them. + +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Limitations +----------- + +1. There is only a very limited subset of packages installed. They are choosen carefully to satisfy most of the Python apps but your app might have some special needs. +1. There is no npm and nodejs. +1. There are no compilers and header files. Installation from Python wheels should still work but compilation from a source code is not supported out of the box. + +In the next chapter, we provide three possible workarounds for the mentioned limitations of the minimal container image. + +Possible solutions for the limitations +-------------------------------------- + +### Use the full container image + +It's easy at that. If you don't want to write your own Dockerfile and disk space is not a problem, use +the full universal container image and you should be fine. + +### Build your own container image on top of the minimal container image + +Let's say that your application depends on uwsgi. uwsgi cannot be installed from Python wheel and has to be +compiled from source which requires some additional packages to be installed - namely gcc for the compilation +itself and {{ spec.pkg_prefix }}-devel containing Python header files. + +To solve that problem, you can use all the pieces provided by the minimal container image and just add one more +step to install the missing dependencies: + +``` +FROM python-{{ spec.short_ver }}-minimal + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src + +# Install packages necessary for compiling uwsgi from source +RUN microdnf install -y gcc {{ spec.pkg_prefix }}-devel +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you do it this way, your problem with the missing packages is solved. But there is also one disadvantage: the resulting +runtime image contains unnecessary compiler and Python header files. How to solve this? Uninstalling them at the end +of the Dockerfile is not really a solution but we have one. Keep reading. + +### Build on full image, run on minimal image + +Did you know that you can copy files from one image to another one during a build? That's the feature we are gonna use now. +We use the full container image with all compilers and other usefull packages installed to build our app and its dependencies +and we then move the result including the whole virtual environemnt to the minimal container image. + +This app needs mod_wsgi and to install (compile it from source) it, we'll need: httpd-devel for header files, gcc and redhat-rpm-config +as a compiler and configuratuion and finally {{ spec.pkg_prefix }}-devel containing Python header files. There is no need to install those packages +manually because the full container image already contains them. However, the application needs httpd as a runtime dependency +so we need to install it to the minimal container image as well. + +``` +# Part 1 - build + +FROM python-{{ spec.short_ver }} as builder + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the application's dependencies from PyPI +RUN /usr/libexec/s2i/assemble + +# Part 2 - deploy + +FROM python-{{ spec.short_ver }}-minimal + +# Copy app sources together with the whole virtual environment from the builder image +COPY --from=builder $APP_ROOT $APP_ROOT + +# Install httpd package - runtime dependency of our application +USER 0 +RUN microdnf install -y httpd +USER 1001 + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +This way, the resulting container image does contain only necessary dependencies and it's much lighter. diff --git a/src/README.md b/src/README.md index dcaa0fce..77d071c7 100644 --- a/src/README.md +++ b/src/README.md @@ -1,11 +1,11 @@ Python {{ spec.version }} container image -=================== +========================= This container image includes Python {{ spec.version }} as a [S2I](https://github.com/openshift/source-to-image) base image for your Python {{ spec.version }} applications. -Users can choose between RHEL and CentOS based builder images. -The RHEL images are available in the [Red Hat Container Catalog](https://access.redhat.com/containers/), -the CentOS images are available on [Docker Hub](https://hub.docker.com/r/centos/), -and the Fedora images are available in [Fedora Registry](https://registry.fedoraproject.org/). +Users can choose between RHEL, CentOS Stream, and Fedora based builder images. +The RHEL images are available in the [Red Hat Container Catalog](https://catalog.redhat.com/software/containers/explore), +CentOS Stream images are available on [Quay.io](https://quay.io/organization/sclorg), +and the Fedora images are available on [Quay.io](https://quay.io/organization/fedora). The resulting image can be run using [podman](https://github.com/containers/libpod) or [docker](http://docker.io). @@ -29,28 +29,132 @@ modules for their web applications. There is no guarantee for any specific npm o version, that is included in the image; those versions can be changed anytime and the nodejs itself is included just to make the npm work. -Usage ---------------------- +See [the Red Hat Enterprise Linux Application Streams Life Cycle page](https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle) for information about support for a particular stream. + +Usage in Openshift +------------------ -For this, we will assume that you are using the supported image, available via `python:{{ spec.version }}` imagestream tag in Openshift. -Building a simple [python-sample-app](https://github.com/sclorg/s2i-python-container/tree/master/{{ spec.version }}/test/setup-test-app) application +For this, we will assume that you are using one of the supported images available via imagestream tags in Openshift, eg. `python:{{ spec.version }}-ubi9` +Building a simple [python-sample-app](https://github.com/sclorg/django-ex.git) application in Openshift can be achieved with the following step: ``` - oc new-app python:{{ spec.version }}~https://github.com/sclorg/s2i-python-container.git --context-dir={{ spec.version }}/test/setup-test-app/ + oc new-app python:{{ spec.version }}-ubi9~https://github.com/sclorg/django-ex.git ``` -The same application can also be built using the standalone [S2I](https://github.com/openshift/source-to-image) application on systems that have it available: +**Accessing the application:** +``` +$ oc get pods +$ oc exec -- curl 127.0.0.1:8080 +``` - ``` - $ s2i build https://github.com/sclorg/s2i-python-container.git --context-dir={{ spec.version }}/test/setup-test-app/ python-sample-app - ``` +Source-to-Image framework and scripts +------------------------------------- +This image supports the [Source-to-Image](https://docs.openshift.com/container-platform/4.14/openshift_images/create-images.html#images-create-s2i_create-images) +(S2I) strategy in OpenShift. The Source-to-Image is an OpenShift framework +which makes it easy to write images that take application source code as +an input, use a builder image like this Python container image, and produce +a new image that runs the assembled application as an output. + +To support the Source-to-Image framework, important scripts are included in the builder image: + +* The `/usr/libexec/s2i/assemble` script inside the image is run to produce a new image with the application artifacts. +The script takes sources of a given application and places them into appropriate directories inside the image. +It utilizes some common patterns in Perl application development (see the **Environment variables** section below). +* The `/usr/libexec/s2i/run` script is set as the default command in the resulting container image (the new image with the application artifacts). +It runs your application according to settings in `APP_MODULE`, `APP_FILE` or `APP_SCRIPT` environment variables or it tries to detect the best +way automatically. + +Building an application using a Dockerfile +------------------------------------------ +Compared to the Source-to-Image strategy, using a Dockerfile is a more +flexible way to build a Python container image with an application. +Use a Dockerfile when Source-to-Image is not sufficiently flexible for you or +when you build the image outside of the OpenShift environment. + +To use the Python image in a Dockerfile, follow these steps: +#### 1. Pull a base builder image to build on -Where `` is the s2i-python image you [downloaded from RHEL, Centos or Fedora registry](../README.md#Download) or [built](../README.md#Build) from these sources. For example ubi8/python-36, centos/python-36-centos7 or f31/python3. +``` +podman pull {{ spec.main_image }} +``` + +#### 2. Pull and application code + +An example application available at https://github.com/sclorg/django-ex.git is used here. Feel free to clone the repository for further experiments. +You can also take a look at code examples in s2i-python-container repository: https://github.com/sclorg/s2i-python-container/tree/master/examples + +``` +git clone https://github.com/sclorg/django-ex.git app-src +``` + +#### 3. Prepare an application inside a container + +This step usually consists of at least these parts: + +* putting the application source into the container +* installing the dependencies +* setting the default command in the resulting image + +For all these three parts, users can either setup all manually and use commands `python` and `pip` explicitly in the Dockerfile, +or users can use the Source-to-Image scripts inside the image. + +The manual way comes with the highest level of flexibility but requires you to know how to work +with modules or software collections manually, how to setup virtual environment with the right version +of Python and many more. On the other hand, using Source-to-Image scripts makes your Dockerfile +prepared for a future flawless switch to a newer or different platform. + +To use the Source-to-Image scripts and build an image using a Dockerfile, create a Dockerfile with this content: + +``` +FROM {{ spec.main_image }} + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you decide not to use the Source-to-Image scripts, you will need to manually tailor the Dockerfile to your application and its needs. +Example Dockerfile for a simple Django application: + +``` +FROM {{ spec.main_image }} + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 +``` + +#### 4. Build a new image from a Dockerfile prepared in the previous step + +``` +podman build -t python-app . +``` + +#### 5. Run the resulting image with final application -**Accessing the application:** ``` -$ curl 127.0.0.1:8080 +podman run -d python-app ``` Environment variables @@ -115,9 +219,9 @@ file inside your source code repository. 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_SETUP_PY_PROCESSING** +* **DISABLE_SETUP_PY_PROCESSING** / **DISABLE_PYPROJECT_TOML_PROCESSING** - Set this to a non-empty value to skip processing of setup.py script if you + Set this to a non-empty value to skip processing of setup.{py,cfg} or pyproject.toml script if you use `-e .` in requirements.txt to trigger its processing or you don't want your application to be installed into site-packages directory. @@ -126,8 +230,7 @@ file inside your source code repository. Set this variable to use [Pipenv](https://github.com/pypa/pipenv), the higher-level Python packaging tool, to manage dependencies of the application. This should be used only if your project contains properly formated Pipfile - and Pipfile.lock.{% if spec.version in ["2.7"] %} (Implies `UPGRADE_PIP_TO_LATEST` to satisfy dependencies of - Pipenv.){% endif %} + and Pipfile.lock. * **PIN_PIPENV_VERSION** @@ -135,14 +238,12 @@ file inside your source code repository. If not set, the latest stable version from PyPI is installed. For example `PIN_PIPENV_VERSION=2018.11.26` installs `pipenv==2018.11.26`. -{% if spec.version.startswith("3.") %} * **ENABLE_MICROPIPENV** Set this variable to use [micropipenv](https://github.com/thoth-station/micropipenv), a lightweight wrapper for pip to support requirements.txt, Pipenv and Poetry lock files or converting them to pip-tools compatible output. Designed for containerized Python applications. Available only for Python 3 images. -{% endif %} * **ENABLE_INIT_WRAPPER** @@ -162,12 +263,18 @@ file inside your source code repository. the custom index, the container will try to install/update them from upstream PyPI afterwards. +* **PORT** + + HTTP(S) port your application should listen on. The default is 8080. + `PORT` is used only for Django development server and for Gunicorn + with the default configutation (no `APP_CONFIG` or `GUNICORN_CMD_ARGS` specified). + * **UPGRADE_PIP_TO_LATEST** Set this variable to a non-empty value to have the 'pip' program and related python packages (setuptools and wheel) 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. + before any Python packages are installed. If not set, the container will use + the stable pip version this container was built with, taken from a recent Fedora release. * **WEB_CONCURRENCY** @@ -200,7 +307,7 @@ However, if these files exist they will affect the behavior of the build process Configures various aspects of the project, including installation of dependencies, as documented - [here](https://packaging.python.org/en/latest/distributing.html#setup-py). + [here](https://packaging.python.org/guides/distributing-packages-using-setuptools/?highlight=distributing#setup-py). For most projects, it is sufficient to simply use `requirements.txt` or `Pipfile`. Set `DISABLE_SETUP_PY_PROCESSING` environment variable to true in order to skip processing of this file. @@ -225,6 +332,9 @@ following ways, in precedence order: If you have both Django and Gunicorn in your requirements, your Django project will automatically be served using Gunicorn. + The default setting for Gunicorn (`--bind=0.0.0.0:$PORT --access-logfile=-`) is applied + only if both `$APP_CONFIG` and `$GUNICORN_CMD_ARGS` are not defined. + * **Django development server** If you have Django in your requirements but don't have Gunicorn, then your @@ -248,7 +358,7 @@ following ways, in precedence order: application. Hot deploy ---------------------- +---------- If you are using Django, hot deploy will work out of the box. @@ -259,7 +369,7 @@ 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 podman's (or docker's) -[exec](https://github.com/containers/libpod/blob/master/docs/podman-exec.1.md) command: +[exec](https://github.com/containers/podman/blob/main/docs/source/markdown/podman-exec.1.md) command: ``` podman exec -it /bin/bash @@ -273,5 +383,6 @@ See also -------- Dockerfile and other sources are available on https://github.com/sclorg/s2i-python-container. In that repository you also can find another versions of Python environment Dockerfiles. -Dockerfile for CentOS is called `Dockerfile`, Dockerfile for RHEL7 is called `Dockerfile.rhel7`, -for RHEL8 it's `Dockerfile.rhel8` and the Fedora Dockerfile is called `Dockerfile.fedora`. +Dockerfile for RHEL8 is called `Dockerfile.rhel8`, for RHEL9 it's `Dockerfile.rhel9`, +for CentOS Stream 9 it's `Dockerfile.c9s`, for CentOS Stream 10 it's `Dockerfile.c10s`, +and the Fedora Dockerfile is called `Dockerfile.fedora`. diff --git a/src/cccp.yml b/src/cccp.yml deleted file mode 100644 index adc11bd0..00000000 --- a/src/cccp.yml +++ /dev/null @@ -1,3 +0,0 @@ -# This is for the purpose of building this container -# on the centos container pipeline. -job-id: python-{{ spec.short_ver }}-centos7 diff --git a/src/centos-stream/macros.tpl b/src/centos-stream/macros.tpl new file mode 100644 index 00000000..39ae041c --- /dev/null +++ b/src/centos-stream/macros.tpl @@ -0,0 +1,43 @@ +{% macro env_metadata() %} +ENV NAME=python3 \ + ARCH=x86_64 + +{% endmacro %} + +{% macro _image_name(spec) %} +{{ spec.org }}/python-{{ spec.short_ver }}-{{ spec.prod -}} +{% endmacro %} + +{% macro labels(spec) %} + com.redhat.component="$NAME" \ + name="{{ _image_name(spec) }}" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir={{ + spec.version }}/test/setup-test-app/ $FGC/$NAME python-sample-app" \ + maintainer="SoftwareCollections.org " +{% endmacro %} + +{% macro venv_setup(spec) %} +RUN python{{ spec.version }} -m venv ${APP_ROOT} && \ +{% if spec.version != "3.6" %} +# Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ +# We have to upgrade pip to a newer verison because \ +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ +# support platforms like ppc64le, aarch64 or armv7 \ +# We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ +# because it's tested better then whatever version from PyPI and contains useful patches. \ +# We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ +# to reinstall itself in the next build phases in the assemble script if user wants the latest version \ +${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +{% endif %} +rm -r /opt/wheels && \ +chown -R 1001:0 ${APP_ROOT} && \ +fix-permissions ${APP_ROOT} -P && \ +# The following echo adds the unset command for the variables set below to the \ +# venv activation script. This prevents the virtual environment from being \ +# activated multiple times and also every time the prompt is rendered. \ +echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" +{% endmacro %} diff --git a/src/centos/macros.tpl b/src/centos/macros.tpl deleted file mode 100644 index 77a28534..00000000 --- a/src/centos/macros.tpl +++ /dev/null @@ -1,47 +0,0 @@ -{% macro env_metadata() %}{% endmacro %} - -{% macro _version_selector(spec) %} -{% if spec.python_img_version %}{{ spec.python_img_version }}{% else %}{{ spec.version }}{% endif %} -{% endmacro %} - -{% macro _component_name(spec) -%} -{% if spec.version.startswith('3.') %}{{ spec.python3_component_prefix }}{% endif -%} -python -{%- if spec.el_version == '8' %}-{% endif -%} -{{ spec.short_ver }}-container -{%- endmacro %} - -{% macro _image_name(spec) %} -{{ spec.org }}/python-{{ spec.short_ver }}{% if spec.el_version == '7' %}-{{ spec.prod }}{% endif %} -{% endmacro %} - -{% macro labels(spec) %} - com.redhat.component="{{ _component_name(spec) }}" \ - name="{{ _image_name(spec) }}" \ - version="{{ _version_selector(spec) }}" \ - usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir={{spec.version }}/test/setup-test-app/ {{ _image_name(spec) }} python-sample-app" \ - {% if spec.version in spec.ubi_versions %} - com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ - {% endif %} - maintainer="SoftwareCollections.org " -{% endmacro %} - -{% macro permissions_setup(spec) %} -{# Enable SCL only for RHEL 7 #} -{% if spec.el_version == '7' %} -RUN source scl_source enable {{ spec.scl }} && \ -{% else %} -RUN \ -{% endif %} -{# Use different virtualenv command of venv module based on Python and platform version #} -{% if spec.version not in ["2.7", "3.6"] %} - python{{ spec.version }} -m venv ${APP_ROOT} && \ -{% elif spec.el_version == '7' %} - virtualenv ${APP_ROOT} && \ -{% else %} - virtualenv-$PYTHON_VERSION ${APP_ROOT} && \ -{% endif %} - chown -R 1001:0 ${APP_ROOT} && \ - fix-permissions ${APP_ROOT} -P && \ - rpm-file-permissions -{% endmacro %} diff --git a/src/fedora/macros.tpl b/src/fedora/macros.tpl index 70145193..3b61bd0d 100644 --- a/src/fedora/macros.tpl +++ b/src/fedora/macros.tpl @@ -1,26 +1,38 @@ {% macro env_metadata() %} ENV NAME=python3 \ - VERSION=0 \ ARCH=x86_64 {% endmacro %} {% macro labels(spec) %} com.redhat.component="$NAME" \ - name="$FGC/$NAME" \ - version="$VERSION" \ + name="fedora/$NAME-{{ spec.short_ver }}" \ usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir={{ - spec.version }}/test/setup-test-app/ $FGC/$NAME python-sample-app" \ + spec.version }}/test/setup-test-app/ quay.io/fedora/$NAME-{{ spec.short_ver }} python-sample-app" \ maintainer="SoftwareCollections.org " {% endmacro %} -{% macro permissions_setup(spec) %} +{% macro venv_setup(spec) %} RUN python{{ spec.version }} -m venv ${APP_ROOT} && \ +{% if spec.version != "3.6" %} +# Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ +# We have to upgrade pip to a newer verison because \ +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ +# support platforms like ppc64le, aarch64 or armv7 \ +# We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ +# because it's tested better then whatever version from PyPI and contains useful patches. \ +# We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ +# to reinstall itself in the next build phases in the assemble script if user wants the latest version \ +${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +{% endif %} +rm -r /opt/wheels && \ chown -R 1001:0 ${APP_ROOT} && \ -fix-permissions ${APP_ROOT} -P - -# For Fedora scl_enable isn't sourced automatically in s2i-core -# so virtualenv needs to be activated this way +fix-permissions ${APP_ROOT} -P && \ +# The following echo adds the unset command for the variables set below to the \ +# venv activation script. This prevents the virtual environment from being \ +# activated multiple times and also every time the prompt is rendered. \ +echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells ENV BASH_ENV="${APP_ROOT}/bin/activate" \ ENV="${APP_ROOT}/bin/activate" \ PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" diff --git a/src/rhel b/src/rhel deleted file mode 120000 index 95e18954..00000000 --- a/src/rhel +++ /dev/null @@ -1 +0,0 @@ -centos \ No newline at end of file diff --git a/src/rhel/macros.tpl b/src/rhel/macros.tpl new file mode 100644 index 00000000..46331e87 --- /dev/null +++ b/src/rhel/macros.tpl @@ -0,0 +1,53 @@ +{% macro env_metadata() %}{% endmacro %} + +{% macro _component_name(spec) -%} +python-{{ spec.short_ver }}-container +{%- endmacro %} + +{% macro _image_name(spec) %} +{{ spec.org }}/python-{{ spec.short_ver }} +{%- endmacro %} + +{% macro labels(spec) %} + com.redhat.component="{{ _component_name(spec) }}" \ + name="{{ _image_name(spec) }}" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir={{spec.version }}/test/setup-test-app/ {{ _image_name(spec) }} python-sample-app" \ + {% if spec.version in spec.get("ubi_versions", []) %} + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi{{ spec.el_version }}-python-{{ spec.short_ver }}" \ + {% endif %} + maintainer="SoftwareCollections.org " +{% endmacro %} + +{% macro venv_setup(spec) %} +RUN \ +{# Use different virtualenv command of venv module based on Python and platform version #} +{% if spec.version != "3.6" %} + python{{ spec.version }} -m venv ${APP_ROOT} && \ +{% else %} + virtualenv-$PYTHON_VERSION ${APP_ROOT} && \ +{% endif %} +{% if spec.version != "3.6" %} + # Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ + # We have to upgrade pip to a newer verison because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +{% endif %} + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions && \ + # The following echo adds the unset command for the variables set below to the \ + # venv activation script. This prevents the virtual environment from being \ + # activated multiple times and also every time the prompt is rendered. \ + echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" +{% endmacro %} diff --git a/src/root/opt/app-root/etc/generate_container_user b/src/root/opt/app-root/etc/generate_container_user index 43ee7ecb..a7fd74d6 100644 --- a/src/root/opt/app-root/etc/generate_container_user +++ b/src/root/opt/app-root/etc/generate_container_user @@ -9,7 +9,7 @@ if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"1001" ]; then 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 + echo "default:x:${USER_ID}:${GROUP_ID}:Default Application User:${HOME}:/bin/bash" >> $NSS_WRAPPER_PASSWD export NSS_WRAPPER_PASSWD export NSS_WRAPPER_GROUP diff --git a/src/root/opt/app-root/etc/scl_enable b/src/root/opt/app-root/etc/scl_enable deleted file mode 100644 index f63e0cb8..00000000 --- a/src/root/opt/app-root/etc/scl_enable +++ /dev/null @@ -1,9 +0,0 @@ -# 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 -if head "/etc/redhat-release" | grep -q "^CentOS Linux release 7" || \ - head "/etc/redhat-release" | grep -q "^Red Hat Enterprise Linux\( Server\)\? release 7"; then - source scl_source enable httpd24 $NODEJS_SCL {{ spec.scl }} -fi -source /opt/app-root/bin/activate diff --git a/src/root/opt/wheels/pip-24.2-py3-none-any.whl b/src/root/opt/wheels/pip-24.2-py3-none-any.whl new file mode 100755 index 00000000..ba32df83 Binary files /dev/null and b/src/root/opt/wheels/pip-24.2-py3-none-any.whl differ diff --git a/src/s2i/bin/assemble b/src/s2i/bin/assemble index 95e01915..1ba7ba0e 100755 --- a/src/s2i/bin/assemble +++ b/src/s2i/bin/assemble @@ -9,7 +9,7 @@ function should_collectstatic() { } function virtualenv_bin() { - {% if spec.version not in ["2.7", "3.6"] %} + {% if spec.version != "3.6" %} # New versions of Python (>3.6) should use venv module # from stdlib instead of virtualenv package python{{ spec.version }} -m venv $1 @@ -31,9 +31,6 @@ function virtualenv_bin() { # (pip script installer). function install_tool() { echo "---> Installing $1 packaging tool ..." - {% if spec.version in ["2.7"] %} - pip install -U virtualenv - {% endif %} VENV_DIR=$HOME/.local/venvs/$1 virtualenv_bin "$VENV_DIR" # First, try to install the tool without --isolated which means that if you @@ -66,20 +63,28 @@ mv /tmp/src/* "$HOME" # set permissions for any installed artifacts fix-permissions /opt/app-root -P -# We have to first upgrade pip to at least 19.3 because: -# * pip < 9 does not support different packages' versions for Python 2/3 -# * pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 wheels +{% if spec.version == "3.6" %} +# Python <3.7 only code, Python 3.7+ uses bundled wheel from Fedora +# We have to first upgrade pip to at least 19.3 because +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels # support platforms like ppc64le, aarch64 or armv7 -echo "---> Upgrading pip to version 19.3.1 ..." -if ! pip install -U "pip==19.3.1"; then - echo "WARNING: Installation of 'pip==19.3.1' failed, trying again from official PyPI with pip --isolated install" - pip install --isolated -U "pip==19.3.1" +echo "---> Upgrading pip to latest version supported for Python $PYTHON_VERSION ..." +if ! pip install -U "pip"; then + echo "WARNING: Installation of 'pip' failed, trying again from official PyPI with pip --isolated install" + pip install --isolated -U "pip" +fi +{% endif %} + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini fi -if [[ ! -z "$UPGRADE_PIP_TO_LATEST"{% if spec.version in ["2.7"] %} || ! -z "$ENABLE_PIPENV"{% endif %} ]]; then - echo "---> Upgrading pip to latest version ..." +if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then + echo "---> Upgrading pip, setuptools and wheel to latest version ..." if ! pip install -U pip setuptools wheel; then - echo "WARNING: Installation of the latest pip,setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" pip install --isolated -U pip setuptools wheel fi fi @@ -107,8 +112,13 @@ elif [[ -f requirements.txt ]]; then pip install -r requirements.txt fi -if [[ -f setup.py && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then - echo "---> Installing application ..." +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." pip install . fi diff --git a/src/s2i/bin/run b/src/s2i/bin/run index ee1e1d3b..2e4ebca2 100755 --- a/src/s2i/bin/run +++ b/src/s2i/bin/run @@ -17,7 +17,13 @@ function should_migrate() { # Guess the number of workers according to the number of cores function get_default_web_concurrency() { + {% if spec.minimal %} + # Using python and just python here because the script has + # platform-python in its shebang which we don't have in the minimal image + limit_vars=$(python /usr/bin/cgroup-limits) + {% else %} limit_vars=$(cgroup-limits) + {% endif %} local $limit_vars if [ -z "${NUMBER_OF_CORES:-}" ]; then echo 1 @@ -43,30 +49,54 @@ 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 + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + APP_HOME=$(readlink -f "${APP_HOME:-.}") # Change the working directory to APP_HOME PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" cd "$APP_HOME" -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" +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 fi - maybe_run_in_init_wrapper "$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) ..." - maybe_run_in_init_wrapper python "$APP_FILE" -else - test -n "$app_file_check" && (>&2 echo "ERROR: file '$app_file_check' not found.") && exit 1 +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi fi # Look for 'manage.py' in the current directory @@ -82,6 +112,11 @@ if should_migrate; then fi fi +# If not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + if is_gunicorn_installed; then setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) # Look for wsgi module in the current directory @@ -94,17 +129,27 @@ if is_gunicorn_installed; then 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" + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" fi fi if is_django_installed; then if [[ -f "$manage_file" ]]; then - echo "---> Serving application with 'manage.py runserver' ..." + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." echo "WARNING: this is NOT a recommended way to run you application in production!" echo "Consider using gunicorn or some other production web server." - maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:8080 + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT else echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." echo "Skipped 'python manage.py runserver'." diff --git a/src/s2i/bin/usage b/src/s2i/bin/usage index 2b2b2af8..410977ac 100755 --- a/src/s2i/bin/usage +++ b/src/s2i/bin/usage @@ -5,14 +5,14 @@ NAMESPACE=centos [[ $DISTRO =~ rhel* ]] && NAMESPACE=rhscl cat < -- curl 127.0.0.1:8080 EOF diff --git a/src/test/from-dockerfile/Dockerfile.tpl b/src/test/from-dockerfile/Dockerfile.tpl new file mode 100644 index 00000000..4249e1ee --- /dev/null +++ b/src/test/from-dockerfile/Dockerfile.tpl @@ -0,0 +1,14 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run diff --git a/src/test/from-dockerfile/Dockerfile_no_s2i.tpl b/src/test/from-dockerfile/Dockerfile_no_s2i.tpl new file mode 100644 index 00000000..da2f43ff --- /dev/null +++ b/src/test/from-dockerfile/Dockerfile_no_s2i.tpl @@ -0,0 +1,16 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 diff --git a/src/test/from-dockerfile/mod_wsgi.Dockerfile.tpl b/src/test/from-dockerfile/mod_wsgi.Dockerfile.tpl new file mode 100644 index 00000000..946157e5 --- /dev/null +++ b/src/test/from-dockerfile/mod_wsgi.Dockerfile.tpl @@ -0,0 +1,28 @@ +# The lowercase "from" is a dirty hack! TODO: Fix it! +# It's needed because ct_test_app_dockerfile changes the source image +# to the one currently in the testing and we need to avoid that here +# so we can prepare our own dockerfile with two different source images. +from #FULL_IMAGE_NAME# as builder + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the application's dependencies from PyPI +RUN /usr/libexec/s2i/assemble + +from #IMAGE_NAME# + +# Copy app sources together with the whole virtual environment from the builder image +COPY --from=builder $APP_ROOT $APP_ROOT + +# Install httpd and echant packages - runtime dependencies of our application +USER 0 +RUN microdnf install -y httpd +USER 1001 + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run diff --git a/src/test/from-dockerfile/uwsgi.Dockerfile.tpl b/src/test/from-dockerfile/uwsgi.Dockerfile.tpl new file mode 100644 index 00000000..ed16117f --- /dev/null +++ b/src/test/from-dockerfile/uwsgi.Dockerfile.tpl @@ -0,0 +1,20 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +ENV UPGRADE_PIP_TO_LATEST=1 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +# Install packages necessary for compiling uwsgi from source +# pkgconfig(python-{{ spec.version }}) is provided by both python3-devel in c9s +# and {{ spec.pkg_prefix }}-devel in UBI8. +RUN microdnf install -y gcc "pkgconfig(python-{{ spec.version }})" which +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run diff --git a/src/test/pin-pipenv-version-test-app/.s2i/environment b/src/test/pin-pipenv-version-test-app/.s2i/environment new file mode 100644 index 00000000..d859ce70 --- /dev/null +++ b/src/test/pin-pipenv-version-test-app/.s2i/environment @@ -0,0 +1,8 @@ +ENABLE_PIPENV=1 +{% if spec.version in ["3.6", "3.8", "3.9"] %} +PIN_PIPENV_VERSION=2021.5.29 +{% elif spec.version in ["3.12", "3.13"] %} +PIN_PIPENV_VERSION=2023.11.14 +{% else %} +PIN_PIPENV_VERSION=2026.0.3 +{% endif %} diff --git a/src/test/pipenv-test-app/2.7/Pipfile.lock b/src/test/pipenv-test-app/2.7/Pipfile.lock deleted file mode 100644 index 3214f173..00000000 --- a/src/test/pipenv-test-app/2.7/Pipfile.lock +++ /dev/null @@ -1,206 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "dc65b6677cd4aa3978aca0fd43e65e444b2d4cfd33a5b2637486f52235157ea0" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "2.7" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" - ], - "version": "==2019.9.11" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "e1839a8": { - "editable": true, - "path": "." - }, - "gunicorn": { - "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" - ], - "version": "==19.9.0" - }, - "idna": { - "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" - ], - "version": "==2.7" - }, - "requests": { - "hashes": [ - "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", - "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" - ], - "index": "pypi", - "version": "==2.20.0" - }, - "urllib3": { - "hashes": [ - "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", - "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" - ], - "version": "==1.24.3" - } - }, - "develop": { - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "version": "==1.3.0" - }, - "attrs": { - "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" - ], - "version": "==19.3.0" - }, - "configparser": { - "hashes": [ - "sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c", - "sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df" - ], - "markers": "python_version < '3'", - "version": "==4.0.2" - }, - "contextlib2": { - "hashes": [ - "sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e", - "sha256:3355078a159fbb44ee60ea80abd0d87b80b78c248643b49aa6d94673b413609b" - ], - "markers": "python_version < '3'", - "version": "==0.6.0.post1" - }, - "funcsigs": { - "hashes": [ - "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", - "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" - ], - "markers": "python_version < '3.0'", - "version": "==1.0.2" - }, - "importlib-metadata": { - "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" - ], - "markers": "python_version < '3.8'", - "version": "==0.23" - }, - "more-itertools": { - "hashes": [ - "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", - "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", - "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9" - ], - "markers": "python_version <= '2.7'", - "version": "==5.0.0" - }, - "packaging": { - "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" - ], - "version": "==19.2" - }, - "pathlib2": { - "hashes": [ - "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db", - "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868" - ], - "markers": "python_version < '3.6'", - "version": "==2.3.5" - }, - "pluggy": { - "hashes": [ - "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", - "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" - ], - "version": "==0.13.0" - }, - "py": { - "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" - ], - "version": "==1.8.0" - }, - "pyparsing": { - "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" - ], - "version": "==2.4.2" - }, - "pytest": { - "hashes": [ - "sha256:5d0d20a9a66e39b5845ab14f8989f3463a7aa973700e6cdf02db69da9821e738", - "sha256:692d9351353ef709c1126266579edd4fd469dcf6b5f4f583050f72161d6f3592" - ], - "index": "pypi", - "version": "==4.6.6" - }, - "scandir": { - "hashes": [ - "sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e", - "sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022", - "sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f", - "sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f", - "sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae", - "sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173", - "sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4", - "sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32", - "sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188", - "sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d", - "sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac" - ], - "markers": "python_version < '3.5'", - "version": "==1.10.0" - }, - "six": { - "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" - ], - "version": "==1.12.0" - }, - "wcwidth": { - "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" - ], - "version": "==0.1.7" - }, - "zipp": { - "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" - ], - "version": "==0.6.0" - } - } -} diff --git a/src/test/pipenv-test-app/3.12/Pipfile.lock b/src/test/pipenv-test-app/3.12/Pipfile.lock new file mode 100644 index 00000000..1d3f18e5 --- /dev/null +++ b/src/test/pipenv-test-app/3.12/Pipfile.lock @@ -0,0 +1,211 @@ +{ + "_meta": { + "hash": { + "sha256": "08628d8adcf5d297b100b9dbb14807af951f225a705b902e39726cbbbaf91616" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.11.17" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "gunicorn": { + "hashes": [ + "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0", + "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033" + ], + "markers": "python_version >= '3.5'", + "version": "==21.2.0" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", + "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.18" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pluggy": { + "hashes": [ + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "pytest": { + "hashes": [ + "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", + "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==7.4.3" + } + } +} diff --git a/src/test/pipenv-test-app/3.13/Pipfile.lock b/src/test/pipenv-test-app/3.13/Pipfile.lock new file mode 100644 index 00000000..de1ccaf4 --- /dev/null +++ b/src/test/pipenv-test-app/3.13/Pipfile.lock @@ -0,0 +1,211 @@ +{ + "_meta": { + "hash": { + "sha256": "1c042fcc7ce0692ac9ad83e0cc00f01a81991e88b45c33d6edc3e55ac79ae443" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.13" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "index": "pypi", + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "editable": true, + "path": "." + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "pluggy": { + "hashes": [ + "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", + "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" + }, + "pytest": { + "hashes": [ + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" + ], + "index": "pypi", + "version": "==8.3.3" + } + } +} diff --git a/src/test/pipenv-test-app/3.14/Pipfile.lock b/src/test/pipenv-test-app/3.14/Pipfile.lock new file mode 100644 index 00000000..11c134ec --- /dev/null +++ b/src/test/pipenv-test-app/3.14/Pipfile.lock @@ -0,0 +1,242 @@ +{ + "_meta": { + "hash": { + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.14" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.0.2" + } + } +} diff --git a/src/test/pipenv-test-app/3.4/Pipfile.lock b/src/test/pipenv-test-app/3.4/Pipfile.lock deleted file mode 100644 index ff44bc04..00000000 --- a/src/test/pipenv-test-app/3.4/Pipfile.lock +++ /dev/null @@ -1,95 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "87826de2f3874f7c3828868c2a47d9315fced0f8a676ca489b7f657d7d41c7c4" - }, - "host-environment-markers": { - "implementation_name": "cpython", - "implementation_version": "3.4.5", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "CPython", - "platform_release": "4.13.5-200.fc26.x86_64", - "platform_system": "Linux", - "platform_version": "#1 SMP Thu Oct 5 16:53:13 UTC 2017", - "python_full_version": "3.4.5", - "python_version": "3.4", - "sys_platform": "linux" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.4" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704", - "sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5" - ], - "version": "==2017.7.27.1" - }, - "chardet": { - "hashes": [ - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" - ], - "version": "==3.0.4" - }, - "e1839a8": { - "editable": true, - "path": "." - }, - "gunicorn": { - "hashes": [ - "sha256:75af03c99389535f218cc596c7de74df4763803f7b63eb09d77e92b3956b36c6", - "sha256:eee1169f0ca667be05db3351a0960765620dad53f53434262ff8901b68a1b622" - ], - "version": "==19.7.1" - }, - "idna": { - "hashes": [ - "sha256:cc19709fd6d0cbfed39ea875d29ba6d4e22c0cebc510a76d6302a28385e8bb70", - "sha256:3cb5ce08046c4e3a560fc02f138d0ac63e00f8ce5901a56b32ec8b7994082aab" - ], - "version": "==2.5" - }, - "requests": { - "hashes": [ - "sha256:73b4088c05f7fb5ca8e68651ed802df3ca40621281acf74bb321b4a8408aab7e", - "sha256:eff227db5864238d44270cbadc8ac4133e69b69a2e7092b7b316ed1e4761cbd6" - ], - "version": "==2.17.0" - }, - "urllib3": { - "hashes": [ - "sha256:8ed6d5c1ff9d6ba84677310060d6a3a78ca3072ce0684cb3c645023009c114b1", - "sha256:b14486978518ca0901a76ba973d7821047409d7f726f22156b24e83fd71382a5" - ], - "version": "==1.21.1" - } - }, - "develop": { - "py": { - "hashes": [ - "sha256:2ccb79b01769d99115aa600d7eed99f524bf752bba8f041dc1c184853514655a", - "sha256:0f2d585d22050e90c7d293b6451c83db097df77871974d90efd5a30dc12fcde3" - ], - "version": "==1.4.34" - }, - "pytest": { - "hashes": [ - "sha256:81a25f36a97da3313e1125fce9e7bbbba565bc7fec3c5beb14c262ddab238ac1", - "sha256:27fa6617efc2869d3e969a3e75ec060375bfb28831ade8b5cdd68da3a741dc3c" - ], - "version": "==3.2.3" - } - } -} diff --git a/src/test/pipenv-test-app/3.5/Pipfile.lock b/src/test/pipenv-test-app/3.5/Pipfile.lock deleted file mode 100644 index 6af2fa50..00000000 --- a/src/test/pipenv-test-app/3.5/Pipfile.lock +++ /dev/null @@ -1,95 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "f1246f5a691fc5fca955313011d3ac03013a39512b3d5df949a3d7f41892f0de" - }, - "host-environment-markers": { - "implementation_name": "cpython", - "implementation_version": "3.5.3", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "CPython", - "platform_release": "4.13.5-200.fc26.x86_64", - "platform_system": "Linux", - "platform_version": "#1 SMP Thu Oct 5 16:53:13 UTC 2017", - "python_full_version": "3.5.3", - "python_version": "3.5", - "sys_platform": "linux" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.5" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704", - "sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5" - ], - "version": "==2017.7.27.1" - }, - "chardet": { - "hashes": [ - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" - ], - "version": "==3.0.4" - }, - "e1839a8": { - "editable": true, - "path": "." - }, - "gunicorn": { - "hashes": [ - "sha256:75af03c99389535f218cc596c7de74df4763803f7b63eb09d77e92b3956b36c6", - "sha256:eee1169f0ca667be05db3351a0960765620dad53f53434262ff8901b68a1b622" - ], - "version": "==19.7.1" - }, - "idna": { - "hashes": [ - "sha256:cc19709fd6d0cbfed39ea875d29ba6d4e22c0cebc510a76d6302a28385e8bb70", - "sha256:3cb5ce08046c4e3a560fc02f138d0ac63e00f8ce5901a56b32ec8b7994082aab" - ], - "version": "==2.5" - }, - "requests": { - "hashes": [ - "sha256:73b4088c05f7fb5ca8e68651ed802df3ca40621281acf74bb321b4a8408aab7e", - "sha256:eff227db5864238d44270cbadc8ac4133e69b69a2e7092b7b316ed1e4761cbd6" - ], - "version": "==2.17.0" - }, - "urllib3": { - "hashes": [ - "sha256:8ed6d5c1ff9d6ba84677310060d6a3a78ca3072ce0684cb3c645023009c114b1", - "sha256:b14486978518ca0901a76ba973d7821047409d7f726f22156b24e83fd71382a5" - ], - "version": "==1.21.1" - } - }, - "develop": { - "py": { - "hashes": [ - "sha256:2ccb79b01769d99115aa600d7eed99f524bf752bba8f041dc1c184853514655a", - "sha256:0f2d585d22050e90c7d293b6451c83db097df77871974d90efd5a30dc12fcde3" - ], - "version": "==1.4.34" - }, - "pytest": { - "hashes": [ - "sha256:81a25f36a97da3313e1125fce9e7bbbba565bc7fec3c5beb14c262ddab238ac1", - "sha256:27fa6617efc2869d3e969a3e75ec060375bfb28831ade8b5cdd68da3a741dc3c" - ], - "version": "==3.2.3" - } - } -} diff --git a/src/test/pipenv-test-app/3.6/Pipfile.lock b/src/test/pipenv-test-app/3.6/Pipfile.lock index dc32658d..9102b17b 100644 --- a/src/test/pipenv-test-app/3.6/Pipfile.lock +++ b/src/test/pipenv-test-app/3.6/Pipfile.lock @@ -18,10 +18,10 @@ "default": { "certifi": { "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" + "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", + "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" ], - "version": "==2019.9.11" + "version": "==2020.12.5" }, "chardet": { "hashes": [ @@ -36,10 +36,11 @@ }, "gunicorn": { "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" + "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", + "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" ], - "version": "==19.9.0" + "markers": "python_version >= '3.5'", + "version": "==20.1.0" }, "idna": { "hashes": [ @@ -56,100 +57,107 @@ "index": "pypi", "version": "==2.20.0" }, + "testapp": { + "editable": true, + "path": "." + }, "urllib3": { "hashes": [ "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'", "version": "==1.24.3" } }, "develop": { - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "version": "==1.3.0" - }, "attrs": { "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "version": "==19.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.3.0" }, "importlib-metadata": { "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" + "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581", + "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d" ], - "markers": "python_version < '3.8'", - "version": "==0.23" + "markers": "python_version < '3.8' and python_version < '3.8'", + "version": "==4.0.1" }, - "more-itertools": { + "iniconfig": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" ], - "version": "==7.2.0" + "version": "==1.1.1" }, "packaging": { "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" + "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", + "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" ], - "version": "==19.2" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.9" }, "pluggy": { "hashes": [ - "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", - "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], - "version": "==0.13.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.1" }, "py": { "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", + "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" ], - "version": "==1.8.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.10.0" }, "pyparsing": { "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "version": "==2.4.2" + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.4.7" }, "pytest": { "hashes": [ - "sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", - "sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4" + "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", + "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" ], "index": "pypi", - "version": "==5.2.2" + "version": "==6.2.3" }, - "six": { + "toml": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "version": "==1.12.0" + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.2" }, - "wcwidth": { + "typing-extensions": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" ], - "version": "==0.1.7" + "markers": "python_version < '3.8'", + "version": "==3.10.0.0" }, "zipp": { "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", + "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" ], - "version": "==0.6.0" + "markers": "python_version >= '3.6'", + "version": "==3.4.1" } } } diff --git a/src/test/pipenv-test-app/3.7/Pipfile.lock b/src/test/pipenv-test-app/3.7/Pipfile.lock deleted file mode 100644 index d5a6f9be..00000000 --- a/src/test/pipenv-test-app/3.7/Pipfile.lock +++ /dev/null @@ -1,155 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "dbe9d3f790514385bed543a5fc1e85759ad0d0e39028c102d82c8d4afc1a5fc3" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.7" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" - ], - "version": "==2019.9.11" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "e1839a8": { - "editable": true, - "path": "." - }, - "gunicorn": { - "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" - ], - "version": "==19.9.0" - }, - "idna": { - "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" - ], - "version": "==2.7" - }, - "requests": { - "hashes": [ - "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", - "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" - ], - "index": "pypi", - "version": "==2.20.0" - }, - "urllib3": { - "hashes": [ - "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", - "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" - ], - "version": "==1.24.3" - } - }, - "develop": { - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "version": "==1.3.0" - }, - "attrs": { - "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" - ], - "version": "==19.3.0" - }, - "importlib-metadata": { - "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" - ], - "markers": "python_version < '3.8'", - "version": "==0.23" - }, - "more-itertools": { - "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" - ], - "version": "==7.2.0" - }, - "packaging": { - "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" - ], - "version": "==19.2" - }, - "pluggy": { - "hashes": [ - "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", - "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" - ], - "version": "==0.13.0" - }, - "py": { - "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" - ], - "version": "==1.8.0" - }, - "pyparsing": { - "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" - ], - "version": "==2.4.2" - }, - "pytest": { - "hashes": [ - "sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", - "sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4" - ], - "index": "pypi", - "version": "==5.2.2" - }, - "six": { - "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" - ], - "version": "==1.12.0" - }, - "wcwidth": { - "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" - ], - "version": "==0.1.7" - }, - "zipp": { - "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" - ], - "version": "==0.6.0" - } - } -} diff --git a/src/test/pipenv-test-app/3.8/Pipfile.lock b/src/test/pipenv-test-app/3.8/Pipfile.lock deleted file mode 100644 index e0bbf2e4..00000000 --- a/src/test/pipenv-test-app/3.8/Pipfile.lock +++ /dev/null @@ -1,133 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "f1d3ff05e8c9143e751780c871a656ef8794c15cf6e8ef08cfa211ad25a24320" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.8" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" - ], - "version": "==2019.11.28" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "e1839a8": { - "editable": true, - "path": "." - }, - "gunicorn": { - "hashes": [ - "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", - "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" - ], - "version": "==20.0.4" - }, - "idna": { - "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" - ], - "version": "==2.7" - }, - "requests": { - "hashes": [ - "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", - "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" - ], - "index": "pypi", - "version": "==2.20.0" - }, - "urllib3": { - "hashes": [ - "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", - "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" - ], - "version": "==1.24.3" - } - }, - "develop": { - "attrs": { - "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" - ], - "version": "==19.3.0" - }, - "more-itertools": { - "hashes": [ - "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", - "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564" - ], - "version": "==8.0.2" - }, - "packaging": { - "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" - ], - "version": "==19.2" - }, - "pluggy": { - "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" - ], - "version": "==0.13.1" - }, - "py": { - "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" - ], - "version": "==1.8.0" - }, - "pyparsing": { - "hashes": [ - "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", - "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" - ], - "version": "==2.4.5" - }, - "pytest": { - "hashes": [ - "sha256:63344a2e3bce2e4d522fd62b4fdebb647c019f1f9e4ca075debbd13219db4418", - "sha256:f67403f33b2b1d25a6756184077394167fe5e2f9d8bdaab30707d19ccec35427" - ], - "index": "pypi", - "version": "==5.3.1" - }, - "six": { - "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" - ], - "version": "==1.13.0" - }, - "wcwidth": { - "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" - ], - "version": "==0.1.7" - } - } -} diff --git a/src/test/pipenv-test-app/Pipfile b/src/test/pipenv-test-app/Pipfile index f8d416bd..d6383329 100644 --- a/src/test/pipenv-test-app/Pipfile +++ b/src/test/pipenv-test-app/Pipfile @@ -5,7 +5,12 @@ name = "pypi" [packages] "e1839a8" = {path = ".", editable = true} +{% if spec.version in ["3.6", "3.8", "3.9"] %} requests = "==2.20.0" +{% else %} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} +{% endif %} [dev-packages] pytest = ">=2.8.0" diff --git a/3.3/test/django-test-app/project/__init__.py b/test/__init__.py similarity index 100% rename from 3.3/test/django-test-app/project/__init__.py rename to test/__init__.py diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 00000000..8af74506 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,120 @@ +import os +import sys +from pathlib import Path +from collections import namedtuple + +from container_ci_suite.utils import check_variables +from pytest import skip + +if not check_variables(): + sys.exit(1) + +TAGS = { + "rhel8": "-ubi8", + "rhel9": "-ubi9", + "rhel10": "-ubi10", +} + + +Vars = namedtuple( + "Vars", + [ + "OS", + "VERSION", + "IMAGE_NAME", + "IS_MINIMAL", + "VERSION_NO_MINIMAL", + "SHORT_VERSION", + "TAG", + "TEST_DIR", + "WEB_APPS", + "SHOULD_FAIL_WEB_APPS", + "UNSTABLE_WEB_APP", + "BRANCH_TO_TEST", + "DEPLOYED_PSQL_IMAGE", + "IMAGE_TAG", + "PSQL_VERSION", + ], +) +VERSION = os.getenv("VERSION") +OS = os.getenv("TARGET").lower() +TEST_DIR = Path(__file__).parent.absolute() +BRANCH_TO_TEST = "4.2.x" +DEPLOYED_PSQL_IMAGE = "quay.io/sclorg/postgresql-12-c8s" +IMAGE_TAG = "postgresql:12" +PSQL_VERSION = "12" + +if VERSION in ("3.6"): + DEPLOYED_PSQL_IMAGE = "quay.io/sclorg/postgresql-10-c8s:c8s" + IMAGE_TAG = "postgresql:10" + PSQL_VERSION = "10" + BRANCH_TO_TEST = "2.2.x" + +COMMON_WEB_APPS = [ + TEST_DIR / f"{x}-test-app" + for x in [ + "gunicorn-config-different-port", + "gunicorn-different-port", + "django-different-port", + "standalone", + "setup", + "setup-requirements", + "django", + "numpy", + "app-home", + "locale", + "app-module", + "pyuwsgi-pipenv", + "micropipenv", + "standalone-custom-pypi-index", + "gunicorn-python-configfile-different-port", + ] +] +FULL_WEB_APPS = [ + TEST_DIR / f"{x}-test-app" + for x in [ + "setup-cfg", + "npm-virtualenv-uwsgi", + "mod-wsgi", + "pin-pipenv-version", + "micropipenv-requirements", + "poetry-src-layout", + ] +] +SHOULD_FAIL_WEB_APPS = [ + TEST_DIR / "pipenv-and-micropipenv-should-fail-test-app", +] +UNSTABLE_WEB_APP = [ + "pipenv-test-app", +] +if "minimal" in VERSION: + WEB_APPS = COMMON_WEB_APPS +else: + WEB_APPS = COMMON_WEB_APPS + FULL_WEB_APPS + +VARS = Vars( + OS=OS, + VERSION=VERSION, + IMAGE_NAME=os.getenv("IMAGE_NAME"), + IS_MINIMAL="minimal" in VERSION, + VERSION_NO_MINIMAL=VERSION.replace("-minimal", ""), + SHORT_VERSION=VERSION.replace("-minimal", "").replace(".", ""), + TAG=TAGS.get(OS), + TEST_DIR=TEST_DIR, + WEB_APPS=WEB_APPS, + SHOULD_FAIL_WEB_APPS=SHOULD_FAIL_WEB_APPS, + UNSTABLE_WEB_APP=UNSTABLE_WEB_APP, + BRANCH_TO_TEST=BRANCH_TO_TEST, + DEPLOYED_PSQL_IMAGE=DEPLOYED_PSQL_IMAGE, + IMAGE_TAG=IMAGE_TAG, + PSQL_VERSION=PSQL_VERSION, +) + + +def skip_helm_charts_tests(): + if VARS.VERSION in ("3.9-minimal") or ( + VARS.VERSION == "3.12-minimal" and VARS.OS == "rhel8" + ): + skip(f"Skipping Helm Charts tests for {VARS.VERSION} on {VARS.OS}.") + if VARS.VERSION in ("3.14", "3.14-minimal"): + skip(f"Skipping Helm Charts tests for {VARS.VERSION} on {VARS.OS}. The image is not GA available yet.") diff --git a/test/run b/test/run index 2c231efb..2020f22e 100755 --- a/test/run +++ b/test/run @@ -6,24 +6,56 @@ # 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 WEB_APPS=({standalone,setup,setup-requirements,django,numpy,app-home,npm-virtualenv-uwsgi,locale,mod-wsgi,pipenv,pipenv-and-micropipenv-should-fail,pin-pipenv-version{% if spec.version.startswith("3.") %},micropipenv,micropipenv-requirements{% endif %}}-test-app) +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,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +{% if spec.minimal %} +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${MINIMAL_WEB_APPS[@]}) +{% else %} +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${FULL_WEB_APPS[@]}) +{% endif %} + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) # TODO: Make command compatible for Mac users -test_dir="$(readlink -zf $(dirname "${BASH_SOURCE[0]}"))" -image_dir=$(readlink -zf ${test_dir}/..) +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile{% if spec.minimal %}_minimal{% endif %} + +" if [[ -z $VERSION ]]; then echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 exit 1 fi -IMAGE_NAME=${IMAGE_NAME:-centos/python-${VERSION//./}-centos7} +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} . test/test-lib.sh -# TODO: This should be part of the image metadata -test_port=8080 - info() { echo -e "\n\e[1m[INFO] $@\e[0m\n" } @@ -50,7 +82,7 @@ run_s2i_build() { prepare() { if ! image_exists ${IMAGE_NAME}; then echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." - exit 1 + return 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. @@ -66,15 +98,6 @@ 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 @@ -82,23 +105,6 @@ cleanup() { fi rm -rf ${test_dir}/${1}/.git } - -check_result() { - # positive test & non-zero exit status = ERROR - # negative test & zero exit status = ERROR - local result="$1" - local type=${2:-positive} # if not defined, we expect possitive test type (zero exit code) - if [[ "$type" == "positive" && "$result" != "0" ]]; then - info "TEST FAILED (${type}), EXPECTED:0 GOT:${result}" - cleanup - exit $result - elif [[ "$type" == "negative" && "$result" == "0" ]]; then - info "TEST FAILED (${type}), EXPECTED: non-zero GOT:${result}" - cleanup - exit 1 - fi -} - wait_for_cid() { local max_attempts=10 local sleep_time=1 @@ -119,7 +125,7 @@ test_s2i_usage() { test_docker_run_usage() { info "Testing 'docker run' usage ..." - docker run ${IMAGE_NAME} &>/dev/null + docker run --rm ${IMAGE_NAME} &>/dev/null } test_scl_usage() { @@ -127,7 +133,7 @@ test_scl_usage() { local expected="$2" local cid_file="$3" - info "Testing the image SCL enable" + info "Testing command availability in the image" 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}'" @@ -167,7 +173,7 @@ test_connection() { } test_application() { - local cid_file=$(mktemp -u --suffix=.cid) + local cid_file="$CID_FILE_DIR"/"$(mktemp -u -p . --suffix .cid)" # Verify that the HTTP connection can be established to test application container run_test_application & @@ -175,70 +181,144 @@ test_application() { wait_for_cid # Some test apps have tests in their startup code so we have to check # that the container starts at all - check_result $? + ct_check_testcase_result $? + + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) - test_scl_usage "python --version" "Python $VERSION." "${cid_file}" - check_result $? + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? + {% if not spec.minimal %} test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? + ct_check_testcase_result $? test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" - check_result $? + ct_check_testcase_result $? + {% endif %} test_connection - check_result $? - cleanup_app + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") } -test_latest_imagestreams() { - local result=1 - info "Testing the latest version in imagestreams" - # Switch to root directory of a container - pushd "${test_dir}/../.." >/dev/null || return 1 - ct_check_latest_imagestreams - result=$? - popd >/dev/null || return 1 - check_result $result +test_from_dockerfile(){ + info "Test from Dockerfile" + {% if spec.version == "3.6" %} + django_example_repo_url="https://github.com/sclorg/django-ex.git@2.2.x" + {% else %} + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + {% endif %} + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? } -# Since we built the candidate image locally, we don't want S2I attempt to pull -# it from Docker hub -s2i_args="--pull-policy=never" +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" -# Verify the 'usage' script is working properly when running the base image with 's2i usage ...' -test_s2i_usage -check_result $? + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. -# Verify the 'usage' script is working properly when running the base image with 'docker run ...' -test_docker_run_usage -check_result $? + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? -# For debugging purposes, this script can be run with one or more arguments -# those arguments list is a sub-set of values in the WEB_APPS array defined above -# Example: ./run app-home-test-app pipenv-test-app -for app in ${@:-${WEB_APPS[@]}}; do - prepare ${app} - run_s2i_build ${app} - RESULT=$? - if [[ "$app" == *"-should-fail-"* ]]; then - # Tests with '-should-fail-' in their name should fail during a build, expecting non-zero exit status - check_result $RESULT "negative" - continue + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? else - check_result $RESULT + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." fi - # test application with default user - test_application +} +test_application_with_user() { # test application with random user CONTAINER_ARGS="--user 12345" test_application +} + +test_application_enable_init_wrapper() { # test application with init wrapper CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue - test_latest_imagestreams + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" - info "All tests for the ${app} finished successfully." cleanup ${app} done -info "All tests finished successfully." +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/test/run-openshift b/test/run-openshift deleted file mode 100755 index 4a09812b..00000000 --- a/test/run-openshift +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -# -# Test the Python image in the OpenShift. -# -# IMAGE_NAME specifies a name of the candidate image used for testing. -# VERSION specifies a version of the python in the candidate image. -# The image has to be available before this script is executed. - -THISDIR=$(dirname ${BASH_SOURCE[0]}) - -source "${THISDIR}/test-lib.sh" -source "${THISDIR}/test-lib-openshift.sh" -source "${THISDIR}/test-lib-python.sh" - -set -eo nounset - -trap ct_os_cleanup EXIT SIGINT - -ct_os_check_compulsory_vars - -ct_os_enable_print_logs - -if [ -z "${EPHEMERAL_TEMPLATES:-}" ]; then - EPHEMERAL_TEMPLATES=" -https://raw.githubusercontent.com/sclorg/django-ex/master/openshift/templates/django-postgresql.json \ -https://raw.githubusercontent.com/openshift/origin/master/examples/quickstarts/django-postgresql.json" -fi - -ct_os_cluster_up - -ct_os_test_s2i_app "${IMAGE_NAME}" "${THISDIR}/standalone-test-app" . "Hello World from standalone WSGI application!" - -ct_os_test_s2i_app "${IMAGE_NAME}" "https://github.com/sclorg/django-ex.git" . 'Welcome to your Django application on OpenShift' - -for template in $EPHEMERAL_TEMPLATES; do - ct_os_test_template_app "$IMAGE_NAME" \ - "$template" \ - python \ - 'Welcome to your Django application on OpenShift' \ - 8080 http 200 "-p SOURCE_REPOSITORY_REF=master -p PYTHON_VERSION=${VERSION} -p POSTGRESQL_VERSION=9.6 -p NAME=python-testing" \ - "centos/postgresql-96-centos7|postgresql:9.6" -done - -# Check the imagestream -test_python_imagestream - -OS_TESTSUITE_RESULT=0 - -ct_os_cluster_down - -# vim: set tabstop=2:shiftwidth=2:expandtab: - diff --git a/test/run-openshift-pytest b/test/run-openshift-pytest new file mode 100755 index 00000000..3687c0e5 --- /dev/null +++ b/test/run-openshift-pytest @@ -0,0 +1,11 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel9) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +cd "${THISDIR}" && python3.12 -m pytest -s -rA --showlocals -vv test_ocp_*.py diff --git a/test/run-openshift-remote-cluster b/test/run-openshift-remote-cluster deleted file mode 100755 index 2596e886..00000000 --- a/test/run-openshift-remote-cluster +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -# -# Test the Python image in the OpenShift. -# -# IMAGE_NAME specifies a name of the candidate image used for testing. -# VERSION specifies a version of the python in the candidate image. -# The image has to be available before this script is executed. - -THISDIR=$(dirname ${BASH_SOURCE[0]}) - -source "${THISDIR}/test-lib.sh" -source "${THISDIR}/test-lib-openshift.sh" -source "${THISDIR}/test-lib-python.sh" - -set -eo nounset - -trap ct_os_cleanup EXIT SIGINT - -ct_os_check_compulsory_vars - -oc status || false "It looks like oc is not properly logged in." - -export CT_SKIP_NEW_PROJECT=true -export CT_SKIP_UPLOAD_IMAGE=true -export CT_NAMESPACE=openshift - -test -n "${IMAGE_NAME-}" || false 'make sure $IMAGE_NAME is defined' -test -n "${VERSION-}" || false 'make sure $VERSION is defined' - -if [ -z "${EPHEMERAL_TEMPLATES:-}" ]; then - EPHEMERAL_TEMPLATES=" -https://raw.githubusercontent.com/sclorg/django-ex/master/openshift/templates/django-postgresql.json \ -https://raw.githubusercontent.com/openshift/origin/master/examples/quickstarts/django-postgresql.json" -fi - -ct_os_test_s2i_app "${IMAGE_NAME}" "https://github.com/sclorg/s2i-python-container.git" "examples/standalone-test-app" "Hello World from standalone WSGI application!" -ct_os_test_s2i_app "${IMAGE_NAME}" "https://github.com/sclorg/django-ex.git" . 'Welcome to your Django application on OpenShift' - -for template in $EPHEMERAL_TEMPLATES; do - ct_os_test_template_app "$IMAGE_NAME" \ - "$template" \ - python \ - 'Welcome to your Django application on OpenShift' \ - 8080 http 200 "-p SOURCE_REPOSITORY_REF=master -p PYTHON_VERSION=${VERSION} -p POSTGRESQL_VERSION=9.6 -p NAME=python-testing" \ - "centos/postgresql-96-centos7|postgresql:9.6" -done - -# Check the imagestream -test_python_imagestream - -OS_TESTSUITE_RESULT=0 - -# vim: set tabstop=2:shiftwidth=2:expandtab: - diff --git a/test/run-pytest b/test/run-pytest new file mode 100755 index 00000000..9810fe89 --- /dev/null +++ b/test/run-pytest @@ -0,0 +1,16 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +if python3 -c 'import sys; sys.exit(0 if sys.version_info < (3,13) else 1)'; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/test/show_all_imagestreams.py b/test/show_all_imagestreams.py new file mode 120000 index 00000000..ba8fe973 --- /dev/null +++ b/test/show_all_imagestreams.py @@ -0,0 +1 @@ +../common/show_all_imagestreams.py \ No newline at end of file diff --git a/test/test-lib-openshift.sh b/test/test-lib-openshift.sh deleted file mode 120000 index f48003b8..00000000 --- a/test/test-lib-openshift.sh +++ /dev/null @@ -1 +0,0 @@ -../common/test-lib-openshift.sh \ No newline at end of file diff --git a/test/test-lib-python.sh b/test/test-lib-python.sh deleted file mode 100644 index 6cf2db3e..00000000 --- a/test/test-lib-python.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# -# Functions for tests for the Python image in OpenShift. -# -# IMAGE_NAME specifies a name of the candidate image used for testing. -# The image has to be available before this script is executed. -# - -THISDIR=$(dirname ${BASH_SOURCE[0]}) - -source "${THISDIR}/test-lib.sh" -source "${THISDIR}/test-lib-openshift.sh" - -# Check the imagestream -function test_python_imagestream() { - case ${OS} in - rhel7|centos7) ;; - *) echo "Imagestream testing not supported for $OS environment." ; return 0 ;; - esac - - ct_os_test_image_stream_quickstart "${THISDIR}/imagestreams/python-${OS%[0-9]*}.json" \ - 'https://raw.githubusercontent.com/sclorg/django-ex/master/openshift/templates/django-postgresql.json' \ - "${IMAGE_NAME}" \ - 'python' \ - 'Welcome to your Django application on OpenShift' \ - 8080 http 200 "-p SOURCE_REPOSITORY_REF=master -p PYTHON_VERSION=${VERSION} -p POSTGRESQL_VERSION=9.6 -p NAME=python-testing" \ - "centos/postgresql-96-centos7|postgresql:9.6" -} - -# vim: set tabstop=2:shiftwidth=2:expandtab: - diff --git a/test/test_container_application.py b/test/test_container_application.py new file mode 100644 index 00000000..f6b85994 --- /dev/null +++ b/test/test_container_application.py @@ -0,0 +1,118 @@ +import pytest + +from container_ci_suite.container_lib import ContainerImage, ContainerTestLib +from container_ci_suite.engines.podman_wrapper import PodmanCLIWrapper + +from conftest import VARS + + +class TestPythonApplication: + @pytest.fixture( + scope="class", params=VARS.WEB_APPS, ids=[app.name for app in VARS.WEB_APPS] + ) + def build_s2i_app(self, request): + container_lib = ContainerTestLib(VARS.IMAGE_NAME) + app_name = request.param.name + s2i_app = container_lib.build_as_df( + app_path=request.param, + s2i_args="--pull-policy=never", + src_image=VARS.IMAGE_NAME, + dst_image=f"{VARS.IMAGE_NAME}-{app_name}", + ) + yield s2i_app + s2i_app.cleanup() + + @pytest.mark.parametrize( + "container_args", + [ + "--user 100001", + "--user 12345", + "-e ENABLE_INIT_WRAPPER=true", + ], + ) + def test_application(self, build_s2i_app, container_args): + """ + Test if container works + Python version is properly set + Application response on specific port and specific URL. + + """ + cid_file_name = build_s2i_app.app_name + assert build_s2i_app.create_container( + cid_file_name=cid_file_name, + container_args=container_args, + ) + assert ContainerImage.wait_for_cid(cid_file_name=cid_file_name) + python_version = PodmanCLIWrapper.podman_run_command_and_remove( + cid_file_name=VARS.IMAGE_NAME, cmd="echo $PYTHON_VERSION" + ).strip() + python_version_output = PodmanCLIWrapper.podman_run_command_and_remove( + cid_file_name=VARS.IMAGE_NAME, cmd="python --version" + ) + assert f"Python {python_version}." in python_version_output + cip = build_s2i_app.get_cip(cid_file_name=cid_file_name) + assert cip + port = 8080 + if "different-port" in build_s2i_app.app_name: + port = 8085 + assert build_s2i_app.test_response(url=f"http://{cip}", port=port) + + +class TestPythonShouldFailApplication: + @pytest.mark.parametrize( + "should_fail_app", + VARS.SHOULD_FAIL_WEB_APPS, + ) + def test_application_should_fail(self, should_fail_app): + """ + Test if build fails for should fail apps + + """ + container_lib = ContainerTestLib(VARS.IMAGE_NAME) + app_name = should_fail_app.name + s2i_app_should_fail = container_lib.build_as_df( + app_path=should_fail_app, + s2i_args="--pull-policy=never", + src_image=VARS.IMAGE_NAME, + dst_image=f"{VARS.IMAGE_NAME}-{app_name}", + ) + assert not s2i_app_should_fail + + +class TestPythonUnstableApplication: + @pytest.mark.parametrize( + "unstable_app", + VARS.UNSTABLE_WEB_APP, + ) + def test_unstable_test(self, unstable_app): + """ + Test if unstable test works + """ + container_lib = ContainerTestLib(VARS.IMAGE_NAME) + s2i_app_unstable = container_lib.build_as_df( + app_path=VARS.TEST_DIR / f"{unstable_app}-test-app", + s2i_args="--pull-policy=never", + src_image=VARS.IMAGE_NAME, + dst_image=f"{VARS.IMAGE_NAME}-{unstable_app}", + ) + if not s2i_app_unstable: + pytest.skip(f"Build failed for unstable app {unstable_app}") + cid_file_name = s2i_app_unstable.app_name + assert s2i_app_unstable.create_container( + cid_file_name=cid_file_name, + container_args=s2i_app_unstable, + ) + python_version = PodmanCLIWrapper.podman_run_command_and_remove( + cid_file_name=VARS.IMAGE_NAME, cmd="echo $PYTHON_VERSION" + ).strip() + assert ContainerImage.wait_for_cid(cid_file_name=cid_file_name) + python_version_output = PodmanCLIWrapper.podman_run_command_and_remove( + cid_file_name=VARS.IMAGE_NAME, cmd="python --version" + ) + assert f"Python {python_version}." in python_version_output + cip = s2i_app_unstable.get_cip(cid_file_name=cid_file_name) + assert cip + port = 8080 + if "different-port" in s2i_app_unstable.app_name: + port = 8085 + assert s2i_app_unstable.test_response(url=f"http://{cip}", port=port) diff --git a/test/test_container_basics.py b/test/test_container_basics.py new file mode 100644 index 00000000..41d99b7f --- /dev/null +++ b/test/test_container_basics.py @@ -0,0 +1,150 @@ +import re + +import pytest + +from container_ci_suite.container_lib import ContainerTestLib +from container_ci_suite.engines.podman_wrapper import PodmanCLIWrapper +from container_ci_suite.utils import ContainerTestLibUtils + +from conftest import VARS + + +class TestS2IPythonContainer: + """ + Test if container works properly + """ + + def setup_method(self): + """ + Setup the test environment. + """ + self.app = ContainerTestLib() + + def teardown_method(self): + """ + Cleanup the test environment. + """ + self.app.cleanup() + + def test_run_s2i_usage(self): + """ + Test if s2i usage works + """ + assert self.app.s2i_usage() + + def test_docker_run_usage(self): + """ + Test if container is runnable + """ + assert ( + PodmanCLIWrapper.call_podman_command( + cmd=f"run --rm {VARS.IMAGE_NAME} &>/dev/null", return_output=False + ) + == 0 + ) + + def test_scl_usage(self): + """ + Test if python --version returns proper output + """ + python_version = PodmanCLIWrapper.podman_run_command_and_remove( + cid_file_name=VARS.IMAGE_NAME, cmd="echo $PYTHON_VERSION" + ).strip() + python_version_output = PodmanCLIWrapper.podman_run_command_and_remove( + cid_file_name=VARS.IMAGE_NAME, cmd="python --version" + ) + assert f"Python {python_version}." in python_version_output + if not VARS.IS_MINIMAL: + assert ( + re.search( + r"v[0-9]*\.[0-9]*\.[0-9]*", + PodmanCLIWrapper.podman_run_command_and_remove( + cid_file_name=VARS.IMAGE_NAME, cmd="node --version" + ), + ) + is not None + ) + assert ( + re.search( + r"^[0-9]*\.[0-9]*\.[0-9]*", + PodmanCLIWrapper.podman_run_command_and_remove( + cid_file_name=VARS.IMAGE_NAME, cmd="npm --version" + ), + ) + is not None + ) + + @pytest.mark.parametrize("dockerfile", ["Dockerfile.tpl", "Dockerfile_no_s2i.tpl"]) + def test_dockerfiles(self, dockerfile): + if "minimal" in VARS.VERSION: + pytest.skip("Skipping tests 'test_dockerfiles' for minimal versions.") + assert self.app.build_test_container( + dockerfile=VARS.TEST_DIR / "from-dockerfile" / dockerfile, + app_url=f"https://github.com/sclorg/django-ex.git@{VARS.BRANCH_TO_TEST}", + app_dir="app-src", + ) + assert self.app.test_app_dockerfile() + cip = self.app.get_cip() + assert cip + assert self.app.test_response( + url=cip, expected_output="Welcome to your Django application on OpenShift" + ) + + def test_minimal_dockerfiles(self): + if "minimal" not in VARS.VERSION: + pytest.skip( + "Skipping tests 'test_minimal_dockerfiles' for non-minimal versions." + ) + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. + assert self.app.build_test_container( + dockerfile=VARS.TEST_DIR / "from-dockerfile/uwsgi.Dockerfile.tpl", + app_url=VARS.TEST_DIR / "uwsgi-test-app", + app_dir="app-src", + app_image_name="uwsgi-test-app", + ) + assert self.app.test_app_dockerfile() + cip = self.app.get_cip(cid_file_name="uwsgi-test-app") + assert cip + assert self.app.test_response( + url=cip, + expected_output="Hello World from uWSGI hosted WSGI application!", + ) + + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + full_image_name = VARS.IMAGE_NAME.replace("-minimal", "") + is_pulled = PodmanCLIWrapper.podman_pull_image( + image_name=full_image_name, loops=1 + ) + print(f"Is image {full_image_name} pulled? {is_pulled}") + if is_pulled: + dockerfile_image_name = ContainerTestLibUtils.update_dockerfile( + dockerfile=VARS.TEST_DIR / "from-dockerfile/mod_wsgi.Dockerfile.tpl", + original_string="#IMAGE_NAME#", + string_to_replace=VARS.IMAGE_NAME, + ) + dockerfile = ContainerTestLibUtils.update_dockerfile( + dockerfile=dockerfile_image_name, + original_string="#FULL_IMAGE_NAME#", + string_to_replace=full_image_name, + ) + assert self.app.build_test_container( + dockerfile=dockerfile, + app_url=VARS.TEST_DIR / "micropipenv-requirements-test-app", + app_dir="app-src", + app_image_name="micropipenv-requirements-test-app", + ) + assert self.app.test_app_dockerfile() + cip = self.app.get_cip(cid_file_name="micropipenv-requirements-test-app") + assert cip + assert self.app.test_response( + url=cip, + expected_output="Hello World from mod_wsgi hosted WSGI application!", + debug=True, + ) + else: + pytest.skip( + f"The full container image for the minimal one {full_image_name} does not exist, skipping the rest of the tests." + ) diff --git a/test/test_ocp_deploy_templates.py b/test/test_ocp_deploy_templates.py new file mode 100644 index 00000000..535598cf --- /dev/null +++ b/test/test_ocp_deploy_templates.py @@ -0,0 +1,46 @@ +import pytest + +from container_ci_suite.openshift import OpenShiftAPI + +from conftest import VARS + + +class TestDeployTemplate: + def setup_method(self): + self.oc_api = OpenShiftAPI( + pod_name_prefix=f"python-{VARS.SHORT_VERSION}-test", + version=VARS.VERSION_NO_MINIMAL, + shared_cluster=True, + ) + + def teardown_method(self): + self.oc_api.delete_project() + + @pytest.mark.parametrize( + "template", ["django-postgresql.json", "django-postgresql-persistent.json"] + ) + def test_python_template_inside_cluster(self, template): + assert self.oc_api.upload_image(VARS.DEPLOYED_PSQL_IMAGE, VARS.IMAGE_TAG) + service_name = f"python-{VARS.SHORT_VERSION}-test" + template_url = self.oc_api.get_raw_url_for_json( + container="django-ex", + dir="openshift/templates", + filename=template, + branch=VARS.BRANCH_TO_TEST, + ) + assert self.oc_api.deploy_template_with_image( + image_name=VARS.IMAGE_NAME, + template=template_url, + name_in_template="python", + openshift_args=[ + f"SOURCE_REPOSITORY_REF={VARS.BRANCH_TO_TEST}", + f"PYTHON_VERSION={VARS.VERSION_NO_MINIMAL}", + f"NAME={service_name}", + f"POSTGRESQL_VERSION={VARS.PSQL_VERSION}", + ], + ) + assert self.oc_api.is_template_deployed(name_in_template=service_name) + assert self.oc_api.check_response_inside_cluster( + name_in_template=service_name, + expected_output="Welcome to your Django application on OpenShift", + ) diff --git a/test/test_ocp_helm_python_django_app.py b/test/test_ocp_helm_python_django_app.py new file mode 100644 index 00000000..9980f70f --- /dev/null +++ b/test/test_ocp_helm_python_django_app.py @@ -0,0 +1,41 @@ +from container_ci_suite.helm import HelmChartsAPI + +from conftest import skip_helm_charts_tests, VARS + + +class TestHelmPythonDjangoAppTemplate: + def setup_method(self): + package_name = "redhat-python-django-application" + self.hc_api = HelmChartsAPI( + path=VARS.TEST_DIR, + package_name=package_name, + tarball_dir=VARS.TEST_DIR, + shared_cluster=True, + ) + self.hc_api.clone_helm_chart_repo( + repo_url="https://github.com/sclorg/helm-charts", + repo_name="helm-charts", + subdir="charts/redhat", + ) + + def teardown_method(self): + self.hc_api.delete_project() + + def test_django_application_helm_test(self): + skip_helm_charts_tests() + self.hc_api.package_name = "redhat-python-imagestreams" + assert self.hc_api.helm_package() + assert self.hc_api.helm_installation() + self.hc_api.package_name = "redhat-python-django-application" + assert self.hc_api.helm_package() + assert self.hc_api.helm_installation( + values={ + "python_version": f"{VARS.VERSION}{VARS.TAG}", + "namespace": self.hc_api.namespace, + "source_repository_ref": VARS.BRANCH_TO_TEST, + } + ) + assert self.hc_api.is_s2i_pod_running(pod_name_prefix="django-example") + assert self.hc_api.test_helm_chart( + expected_str=["Welcome to your Django application"] + ) diff --git a/test/test_ocp_helm_python_django_psql_persistent.py b/test/test_ocp_helm_python_django_psql_persistent.py new file mode 100644 index 00000000..df388c84 --- /dev/null +++ b/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1,44 @@ +from container_ci_suite.helm import HelmChartsAPI + +from conftest import skip_helm_charts_tests, VARS + + +class TestHelmPythonDjangoPsqlTemplate: + def setup_method(self): + package_name = "redhat-django-psql-persistent" + self.hc_api = HelmChartsAPI( + path=VARS.TEST_DIR, + package_name=package_name, + tarball_dir=VARS.TEST_DIR, + shared_cluster=True, + ) + self.hc_api.clone_helm_chart_repo( + repo_url="https://github.com/sclorg/helm-charts", + repo_name="helm-charts", + subdir="charts/redhat", + ) + + def teardown_method(self): + self.hc_api.delete_project() + + def test_django_psql_helm_test(self): + skip_helm_charts_tests() + self.hc_api.package_name = "redhat-postgresql-imagestreams" + assert self.hc_api.helm_package() + assert self.hc_api.helm_installation() + self.hc_api.package_name = "redhat-python-imagestreams" + assert self.hc_api.helm_package() + assert self.hc_api.helm_installation() + self.hc_api.package_name = "redhat-django-psql-persistent" + assert self.hc_api.helm_package() + assert self.hc_api.helm_installation( + values={ + "python_version": f"{VARS.VERSION}{VARS.TAG}", + "namespace": self.hc_api.namespace, + "source_repository_ref": VARS.BRANCH_TO_TEST, + } + ) + assert self.hc_api.is_s2i_pod_running(pod_name_prefix="django-psql") + assert self.hc_api.test_helm_chart( + expected_str=["Welcome to your Django application"] + ) diff --git a/test/test_ocp_helm_python_imagestreams.py b/test/test_ocp_helm_python_imagestreams.py new file mode 100644 index 00000000..9ca0424d --- /dev/null +++ b/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1,42 @@ +import pytest + +from container_ci_suite.helm import HelmChartsAPI + +from conftest import VARS + + +class TestHelmRHELPythonImageStreams: + def setup_method(self): + package_name = "redhat-python-imagestreams" + self.hc_api = HelmChartsAPI( + path=VARS.TEST_DIR, + package_name=package_name, + tarball_dir=VARS.TEST_DIR, + shared_cluster=True, + ) + self.hc_api.clone_helm_chart_repo( + repo_url="https://github.com/sclorg/helm-charts", + repo_name="helm-charts", + subdir="charts/redhat", + ) + + def teardown_method(self): + self.hc_api.delete_project() + + @pytest.mark.parametrize( + "version,registry", + [ + ( + "3.12-minimal-ubi10", + "registry.redhat.io/ubi10/python-312-minimal:latest", + ), + ("3.12-ubi9", "registry.redhat.io/ubi9/python-312:latest"), + ("3.12-ubi8", "registry.redhat.io/ubi8/python-312:latest"), + ("3.9-ubi9", "registry.redhat.io/ubi9/python-39:latest"), + ("3.6-ubi8", "registry.redhat.io/ubi8/python-36:latest"), + ], + ) + def test_package_imagestream(self, version, registry): + assert self.hc_api.helm_package() + assert self.hc_api.helm_installation() + assert self.hc_api.check_imagestreams(version=version, registry=registry) diff --git a/test/test_ocp_imagestreams_quickstart.py b/test/test_ocp_imagestreams_quickstart.py new file mode 100644 index 00000000..50138108 --- /dev/null +++ b/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1,63 @@ +import pytest + +from container_ci_suite.openshift import OpenShiftAPI + +from conftest import VARS + + +class TestImagestreamsQuickstart: + def setup_method(self): + self.oc_api = OpenShiftAPI( + pod_name_prefix=f"python-{VARS.SHORT_VERSION}-test", + version=VARS.VERSION_NO_MINIMAL, + shared_cluster=True, + ) + + def teardown_method(self): + self.oc_api.delete_project() + + @pytest.mark.parametrize( + "template", ["django.json", "django-postgresql-persistent.json"] + ) + def test_python_template_inside_cluster(self, template): + if VARS.VERSION in ("3.14", "3.14-minimal"): + pytest.skip(f"Testing templates for {VARS.VERSION} is not yet GA.") + if self.oc_api.shared_cluster: + assert self.oc_api.upload_image_to_external_registry( + VARS.DEPLOYED_PSQL_IMAGE, VARS.IMAGE_TAG + ) + else: + assert self.oc_api.upload_image(VARS.DEPLOYED_PSQL_IMAGE, VARS.IMAGE_TAG) + service_name = f"python-{VARS.SHORT_VERSION}-test" + template_url = self.oc_api.get_raw_url_for_json( + container="django-ex", + dir="openshift/templates", + filename=template, + branch=VARS.BRANCH_TO_TEST, + ) + openshift_args = [ + f"SOURCE_REPOSITORY_REF={VARS.BRANCH_TO_TEST}", + f"PYTHON_VERSION={VARS.VERSION_NO_MINIMAL}", + f"NAME={service_name}", + ] + if template != "django.json": + openshift_args = [ + f"SOURCE_REPOSITORY_REF={VARS.BRANCH_TO_TEST}", + f"POSTGRESQL_VERSION={VARS.PSQL_VERSION}", + f"PYTHON_VERSION={VARS.VERSION_NO_MINIMAL}", + f"NAME={service_name}", + "DATABASE_USER=testu", + "DATABASE_PASSWORD=testp", + ] + assert self.oc_api.imagestream_quickstart( + imagestream_file="imagestreams/python-rhel.json", + template_file=template_url, + image_name=VARS.IMAGE_NAME, + name_in_template="python", + openshift_args=openshift_args, + ) + assert self.oc_api.is_template_deployed(name_in_template=service_name) + assert self.oc_api.check_response_inside_cluster( + name_in_template=service_name, + expected_output="Welcome to your Django application on OpenShift", + ) diff --git a/test/test_ocp_latest_imagestreams.py b/test/test_ocp_latest_imagestreams.py new file mode 100644 index 00000000..ef1cd424 --- /dev/null +++ b/test/test_ocp_latest_imagestreams.py @@ -0,0 +1,14 @@ +from container_ci_suite.imagestreams import ImageStreamChecker + +from conftest import VARS + + +# Replacement with 'test_latest_imagestreams' +class TestLatestImagestreams: + def setup_method(self): + self.isc = ImageStreamChecker(working_dir=VARS.TEST_DIR.parent.parent) + + def test_latest_imagestream(self): + self.latest_version = self.isc.get_latest_version() + assert self.latest_version + self.isc.check_imagestreams(self.latest_version) diff --git a/test/test_ocp_python_ex_standalone.py b/test/test_ocp_python_ex_standalone.py new file mode 100644 index 00000000..4973f7fd --- /dev/null +++ b/test/test_ocp_python_ex_standalone.py @@ -0,0 +1,30 @@ +from container_ci_suite.openshift import OpenShiftAPI + +from conftest import VARS + + +# Replacement with 'test_python_s2i_app_ex_standalone' +class TestPythonExTemplate: + def setup_method(self): + self.oc_api = OpenShiftAPI( + pod_name_prefix=f"python-{VARS.SHORT_VERSION}-test", + version=VARS.VERSION, + shared_cluster=True, + ) + + def teardown_method(self): + self.oc_api.delete_project() + + def test_python_ex_template_inside_cluster(self): + service_name = f"python-{VARS.SHORT_VERSION}-test" + assert self.oc_api.deploy_s2i_app( + image_name=VARS.IMAGE_NAME, + app="https://github.com/sclorg/s2i-python-container.git", + context="examples/standalone-test-app", + service_name=service_name, + ) + assert self.oc_api.is_template_deployed(name_in_template=service_name) + assert self.oc_api.check_response_inside_cluster( + name_in_template=service_name, + expected_output="Hello World from standalone WSGI application!", + ) diff --git a/test/test_ocp_python_ex_template.py b/test/test_ocp_python_ex_template.py new file mode 100644 index 00000000..fe557ce9 --- /dev/null +++ b/test/test_ocp_python_ex_template.py @@ -0,0 +1,30 @@ +from container_ci_suite.openshift import OpenShiftAPI + +from conftest import VARS + + +# Replacement with 'test_python_s2i_app_ex' +class TestPythonExTemplate: + def setup_method(self): + self.oc_api = OpenShiftAPI( + pod_name_prefix=f"python-{VARS.SHORT_VERSION}-test", + version=VARS.VERSION_NO_MINIMAL, + shared_cluster=True, + ) + + def teardown_method(self): + self.oc_api.delete_project() + + def test_python_ex_template_inside_cluster(self): + service_name = f"python-{VARS.SHORT_VERSION}-test" + assert self.oc_api.deploy_s2i_app( + image_name=VARS.IMAGE_NAME, + app=f"https://github.com/sclorg/django-ex.git#{VARS.BRANCH_TO_TEST}", + context=".", + service_name=service_name, + ) + assert self.oc_api.is_template_deployed(name_in_template=service_name) + assert self.oc_api.check_response_inside_cluster( + name_in_template=service_name, + expected_output="Welcome to your Django application on OpenShift", + )