diff --git a/2.7 b/2.7 new file mode 120000 index 0000000..945c9b4 --- /dev/null +++ b/2.7 @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..134f35d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +FROM registry.fedoraproject.org/f33/s2i-base:latest + +# This image provides a Ruby environment you can use to run your Ruby +# applications. + +EXPOSE 8080 + +ENV NAME=ruby \ + RUBY_VERSION=2.7 \ + RUBY_SHORT_VER=27 \ + VERSION=0 + +ENV SUMMARY="Platform for building and running Ruby $RUBY_VERSION applications" \ + DESCRIPTION="Ruby $RUBY_VERSION available as container is a base platform for \ +building and running various Ruby $RUBY_VERSION applications and frameworks. \ +Ruby is the interpreted scripting language for quick and easy object-oriented programming. \ +It has many features to process text files and to do system management tasks (as in Perl). \ +It is simple, straight-forward, and extensible." + +LABEL summary="$SUMMARY" \ + description="$DESCRIPTION" \ + io.k8s.description="$DESCRIPTION" \ + io.k8s.display-name="Ruby ${RUBY_VERSION}" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,ruby,ruby${RUBY_SHORT_VER}" \ + com.redhat.component="$NAME" \ + name="$FGC/$NAME" \ + version="$VERSION" \ + usage="s2i build https://github.com/sclorg/s2i-ruby-container.git --context-dir=${RUBY_VERSION}/test/puma-test-app/ registry.fedoraproject.org/$FGC/ruby ruby-sample-app" \ + maintainer="SoftwareCollections.org " + +# Install required packages +RUN INSTALL_PKGS="ruby ruby-devel rubygem-bundler rubygem-rake rubygems-devel redhat-rpm-config libffi-devel" && \ + dnf install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + dnf clean all + +# 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/ / + +# Drop the root user and make the content of /opt/app-root owned by user 1001 +RUN chown -R 1001:0 ${APP_ROOT} && chmod -R ug+rwx ${APP_ROOT} && \ + 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/Dockerfile.fedora b/Dockerfile.fedora new file mode 120000 index 0000000..1d1fe94 --- /dev/null +++ b/Dockerfile.fedora @@ -0,0 +1 @@ +Dockerfile \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..40570e9 --- /dev/null +++ b/README.md @@ -0,0 +1,231 @@ +Ruby 2.7 container image +======================== +This container image includes Ruby 2.7 as a [S2I](https://github.com/openshift/source-to-image) base image for your Ruby 2.7 applications. +Users can choose between RHEL, CentOS and Fedora 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 [Quay.io](https://quay.io/organization/centos7), +and the Fedora images are available in [Fedora Registry](https://registry.fedoraproject.org/). +The resulting image can be run using [podman](https://github.com/containers/libpod). + +Note: while the examples in this README are calling `podman`, you can replace any such calls by `docker` with the same arguments +Note: While the examples in this README are using UBI8-based images (eg. ubi8/ruby-27), you can substitute any of the ubi8 images +for any other version of the same image (RHSCL, UBI7, Centos7 or Fedora), if it exists. + +Description +----------- + +Ruby 2.7 available as container is a base platform for +building and running various Ruby 2.7 applications and frameworks. +Ruby is the interpreted scripting language for quick and easy object-oriented programming. +It has many features to process text files and to do system management tasks (as in Perl). +It is simple, straight-forward, and extensible. + +This container image includes an npm utility, so users can use it to install JavaScript +modules for their web applications. There is no guarantee for any specific npm or nodejs +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 in Openshift +------------------ +For this, we will assume that you are using the `ubi8/ruby-27 image`, available via `ruby:2.7` imagestream tag in Openshift. +Building a simple [ruby-sample-app](https://github.com/sclorg/s2i-ruby-container/tree/master/2.7/test/puma-test-app) application +in Openshift can be achieved with the following step: + + ``` + oc new-app ruby:2.7~https://github.com/sclorg/rails-ex.git + ``` + +**Accessing the application:** +``` +$ oc get pods +$ oc exec -- curl 127.0.0.1:8080 +``` + +Source-to-Image framework and scripts +------------------------------------- +This image supports the [Source-to-Image](https://docs.openshift.com/container-platform/4.4/builds/build-strategies.html#images-create-s2i_build-strategies) +(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 Ruby 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 Ruby 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 configures web server and runs this server on port 8080. + +Building an application using a Dockerfile +------------------------------------------ +Compared to the Source-to-Image strategy, using a Dockerfile is a more +flexible way to build a Ruby 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 Ruby image in a Dockerfile, follow these steps: + +#### 1. Pull a base builder image to build on + +``` +podman pull ubi8/ruby-27 +``` + +An RHEL7 image `ubi8/ruby-27` is used in this example. + +#### 2. Pull and application code + + An example application available at https://github.com/sclorg/rails-ex.git is used here. Feel free to clone the repository for further experiments. + +``` +git clone https://github.com/sclorg/rails-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 use the Source-to-Image scripts inside the image ([3.1.](#31-to-use-the-source-to-image-scripts-and-build-an-image-using-a-dockerfile-create-a-dockerfile-with-this-content)), or users can either setup all manually and use commands `ruby`, `bundle` and `rackup` explicitly in the Dockerfile ([3.2.](#32-to-use-your-own-setup-create-a-dockerfile-with-this-content)) + +##### 3.1 To use the Source-to-Image scripts and build an image using a Dockerfile, create a Dockerfile with this content: +``` +FROM ubi8/ruby-27 + +# Add application sources to a directory that the assemble scriptexpects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN chown -R 1001:0 /tmp/src +USER 1001 + +# Set up development mode +ENV RAILS_ENV=development + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` +The s2i scripts are used to set-up and run common Ruby applications. More information about the scripts can be found in [Source-to-Image](#source-to-image-framework-and-scripts) section. +##### 3.2 To use your own setup, create a Dockerfile with this content: +``` +FROM ubi8/ruby-27 + +USER 0 +ADD app-src ./ +RUN bundle install --path ./bundle + +CMD bundle exec "rackup -P /tmp/rack.pid --host 0.0.0.0 --port 8080" +``` +#### 4. Build a new image from a Dockerfile prepared in the previous step + +``` +podman build -t ruby-app . +``` + +#### 5. Run the resulting image with final application +``` +podman run -d ruby-app +``` +Environment variables +--------------------- + +To set these environment variables, you can place them as a key value pair into a `.s2i/environment` +file inside your source code repository. + +* **RACK_ENV** + + This variable specifies the environment where the Ruby application will be deployed (unless overwritten) - `production`, `development`, `test`. + Each level has different behaviors in terms of logging verbosity, error pages, ruby gem installation, etc. + + **Note**: Application assets will be compiled only if the `RACK_ENV` is set to `production` + +* **DISABLE_ASSET_COMPILATION** + + This variable set to `true` indicates that the asset compilation process will be skipped. Since this only takes place + when the application is run in the `production` environment, it should only be used when assets are already compiled. + +* **PUMA_MIN_THREADS**, **PUMA_MAX_THREADS** + + These variables indicate the minimum and maximum threads that will be available in [Puma](https://github.com/puma/puma)'s thread pool. + +* **PUMA_WORKERS** + + This variable indicate the number of worker processes that will be launched. See documentation on Puma's [clustered mode](https://github.com/puma/puma#clustered-mode). + +* **RUBYGEM_MIRROR** + + Set this variable to use a custom RubyGems mirror URL to download required gem packages during build process. + +Hot deploy +---------- +In order to dynamically pick up changes made in your application source code, you need to make following steps: + +* **For Ruby on Rails applications** + + Run the built Rails image with the `RAILS_ENV=development` environment variable passed to the [podman](https://github.com/containers/libpod) `-e` run flag: + ``` + $ podman run -e RAILS_ENV=development -p 8080:8080 rails-app + ``` +* **For other types of Ruby applications (Sinatra, Padrino, etc.)** + + Your application needs to be built with one of gems that reloads the server every time changes in source code are done inside the running container. Those gems are: + * [Shotgun](https://github.com/rtomayko/shotgun) + * [Rerun](https://github.com/alexch/rerun) + * [Rack-livereload](https://github.com/johnbintz/rack-livereload) + + Please note that in order to be able to run your application in development mode, you need to modify the [S2I run script](https://github.com/openshift/source-to-image#anatomy-of-a-builder-image), so the web server is launched by the chosen gem, which checks for changes in the source code. + + After you built your application image with your version of [S2I run script](https://github.com/openshift/source-to-image#anatomy-of-a-builder-image), run the image with the RACK_ENV=development environment variable passed to the [podman](https://github.com/containers/libpod) -e run flag: + ``` + $ podman run -e RACK_ENV=development -p 8080:8080 sinatra-app + ``` + +To change your source code in running container, use Podman's [exec](https://github.com/containers/libpod) command: +``` +$ podman exec -it /bin/bash +``` + +After you [podman exec](https://github.com/containers/libpod) into the running container, your current +directory is set to `/opt/app-root/src`, where the source code is located. + +Performance tuning +------------------ +You can tune the number of threads per worker using the +`PUMA_MIN_THREADS` and `PUMA_MAX_THREADS` environment variables. +Additionally, the number of worker processes is determined by the number of CPU +cores that the container has available, as recommended by +[Puma](https://github.com/puma/puma)'s documentation. This is determined using +the cgroup [cpusets](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt) +subsystem. You can specify the cores that the container is allowed to use by passing +the `--cpuset-cpus` parameter to the [podman](https://github.com/containers/libpod) run command: +``` +$ podman run -e PUMA_MAX_THREADS=32 --cpuset-cpus='0-2,3,5' -p 8080:8080 sinatra-app +``` +The number of workers is also limited by the memory limit that is enforced using +cgroups. The builder image assumes that you will need 50 MiB as a base and +another 15 MiB for every worker process plus 128 KiB for each thread. Note that +each worker has its own threads, so the total memory required for the whole +container is computed using the following formula: + +``` +50 + 15 * WORKERS + 0.125 * WORKERS * PUMA_MAX_THREADS +``` +You can specify a memory limit using the `--memory` flag: +``` +$ podman run -e PUMA_MAX_THREADS=32 --memory=300m -p 8080:8080 sinatra-app +``` +If memory is more limiting then the number of available cores, the number of +workers is scaled down accordingly to fit the above formula. The number of +workers can also be set explicitly by setting `PUMA_WORKERS`. + + +See also +-------- +Dockerfile and other sources are available on https://github.com/sclorg/s2i-ruby-container. +In that repository you also can find another versions of Ruby 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. diff --git a/help.md b/help.md new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/help.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/root/opt/app-root/.gemrc b/root/opt/app-root/.gemrc new file mode 100644 index 0000000..2422bea --- /dev/null +++ b/root/opt/app-root/.gemrc @@ -0,0 +1,9 @@ +--- +install: --no-document +update: --no-document +:benchmark: false +:update_sources: true +:bulk_threshold: 1000 +:backtrace: true +:sources: +- https://rubygems.org/ diff --git a/root/opt/app-root/etc/puma.cfg b/root/opt/app-root/etc/puma.cfg new file mode 100644 index 0000000..a52d26d --- /dev/null +++ b/root/opt/app-root/etc/puma.cfg @@ -0,0 +1,50 @@ +def get_max_memory() + return ENV['MEMORY_LIMIT_IN_BYTES'].to_i if ENV.has_key? 'MEMORY_LIMIT_IN_BYTES' + + # Assume unlimited memory. 0.size is the number of bytes a Ruby + # Fixnum class can hold. One bit is used for sign and one is used + # by Ruby to determine whether it's a number or pointer to an object. + # That's why we subtract two bits. This expresion should therefore be + # the largest signed Fixnum possible. + (2 ** (8*0.size - 2) - 1) +end + +def get_memory_per_worker() + bytes = ENV.fetch('MEMORY_BYTES_PER_WORKER', '0').to_i + if bytes == 0 + # Comment describing rationale for choosing default of 256MiB/worker is below. + bytes = 256 * (2**20) + end + bytes +end + +def get_min_threads() + ENV.fetch('PUMA_MIN_THREADS', '0').to_i +end + +def get_max_threads() + ENV.fetch('PUMA_MAX_THREADS', '16').to_i +end + +# Determine the maximum number of workers that are allowed by the available +# memory. Puma documentation recommends the maximum number of workers to be +# set to the number of cores. +# Unless we're specifically tuned otherwise, allow one worker process per 256MiB +# memory, to a maximum of 1 worker / core. Hopefully that'll be a reasonable +# starting point for average apps; if not, it's all tunable. The simple +# OpenShift ruby/rails sample app currently requires approx. 60MiB + +# 70MiB/worker before taking its first request, so hopefully a default of +# 256MiB/worker will give other simple apps reasonable default headroom. +def get_workers() + return ENV['PUMA_WORKERS'].to_i if ENV.has_key? 'PUMA_WORKERS' + + cores = ENV.fetch('NUMBER_OF_CORES', '1').to_i + max_workers = get_max_memory() / get_memory_per_worker() + + [cores, max_workers].min +end + +environment ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'production' +threads get_min_threads(), get_max_threads() +workers get_workers() +bind 'tcp://0.0.0.0:8080' diff --git a/root/opt/app-root/etc/scl_enable b/root/opt/app-root/etc/scl_enable new file mode 100644 index 0000000..70040b4 --- /dev/null +++ b/root/opt/app-root/etc/scl_enable @@ -0,0 +1,6 @@ +# IMPORTANT: Do not add more content to this file unless you know what you are +# doing. This file is sourced everytime the shell session is opened. +# +# This will make scl collection binaries work out of box. +unset BASH_ENV PROMPT_COMMAND ENV +source scl_source enable $NODEJS_SCL rh-ruby27 diff --git a/s2i/bin/assemble b/s2i/bin/assemble new file mode 100755 index 0000000..c2d292c --- /dev/null +++ b/s2i/bin/assemble @@ -0,0 +1,67 @@ +#!/bin/bash + +function rake_assets_precompile() { + [[ "$DISABLE_ASSET_COMPILATION" == "true" ]] && return + [ ! -f Gemfile ] && return + [ ! -f Rakefile ] && return + ! grep " rake " Gemfile.lock >/dev/null && return + ! bundle exec 'rake -T' | grep "assets:precompile" >/dev/null && return + + echo "---> Starting asset compilation ..." + bundle exec "rake assets:precompile" +} + +set -e + +export RACK_ENV=${RACK_ENV:-"production"} + +if [ -n "$RUBYGEM_MIRROR" ]; then + bundle config mirror.https://rubygems.org $RUBYGEM_MIRROR +fi + +shopt -s dotglob +echo "---> Installing application source ..." +mv /tmp/src/* ./ + +# Change the npm registry mirror if provided +if [ -n "$NPM_MIRROR" ]; then + npm config set registry $NPM_MIRROR +fi + +echo "---> Building your Ruby application from source ..." +if [ -f Gemfile ]; then + ADDTL_BUNDLE_ARGS="--retry 2" + if [ -f Gemfile.lock ]; then + ADDTL_BUNDLE_ARGS+=" --deployment" + fi + + if [[ "$RAILS_ENV" == "development" || "$RACK_ENV" == "development" ]]; then + BUNDLE_WITHOUT=${BUNDLE_WITHOUT:-"test"} + elif [[ "$RAILS_ENV" == "test" || "$RACK_ENV" == "test" ]]; then + BUNDLE_WITHOUT=${BUNDLE_WITHOUT:-"development"} + else + BUNDLE_WITHOUT=${BUNDLE_WITHOUT:-"development:test"} + fi + + if [ -n "$BUNDLE_WITHOUT" ]; then + ADDTL_BUNDLE_ARGS+=" --without $BUNDLE_WITHOUT" + fi + + echo "---> Running 'bundle install ${ADDTL_BUNDLE_ARGS}' ..." + bundle install --path ./bundle ${ADDTL_BUNDLE_ARGS} + + echo "---> Cleaning up unused ruby gems ..." + bundle clean -V +fi + +if ! bundle exec rackup -h &>/dev/null; then + echo "WARNING: Rubygem Rack is not installed in the present image." + echo " Add rack to your Gemfile in order to start the web server." +fi + +if [[ "$RAILS_ENV" == "production" || "$RACK_ENV" == "production" ]]; then + rake_assets_precompile +fi + +# Fix source directory permissions +fix-permissions ./ diff --git a/s2i/bin/run b/s2i/bin/run new file mode 100755 index 0000000..1614ec3 --- /dev/null +++ b/s2i/bin/run @@ -0,0 +1,39 @@ +#!/bin/bash + +function is_puma_installed() { + [ ! -f Gemfile.lock ] && return 1 + grep ' puma ' Gemfile.lock >/dev/null +} + +set -e + +function check_number() { + if [[ ! "$2" =~ ^[0-9]+$ ]]; then + echo "$1 needs to be a non-negative number" + exit 1 + fi +} +check_number PUMA_WORKERS "${PUMA_WORKERS:-0}" +check_number PUMA_MIN_THREADS "${PUMA_MIN_THREADS:-0}" +check_number PUMA_MAX_THREADS "${PUMA_MAX_THREADS:-0}" + +export RACK_ENV=${RACK_ENV:-"production"} + +if is_puma_installed; then + export_vars=$(cgroup-limits) ; export $export_vars + + exec bundle exec "puma --config ../etc/puma.cfg" +else + echo "You might consider adding 'puma' into your Gemfile." + + if bundle exec rackup -h &>/dev/null; then + if [ -f Gemfile ]; then + exec bundle exec "rackup -E ${RAILS_ENV:-$RACK_ENV} -P /tmp/rack.pid --host 0.0.0.0 --port 8080" + else + exec rackup -E "${RAILS_ENV:-$RACK_ENV}" -P /tmp/rack.pid --host 0.0.0.0 --port 8080 + fi + else + echo "ERROR: Rubygem Rack is not installed in the present image." + echo " Add rack to your Gemfile in order to start the web server." + fi +fi diff --git a/s2i/bin/usage b/s2i/bin/usage new file mode 100755 index 0000000..76b9beb --- /dev/null +++ b/s2i/bin/usage @@ -0,0 +1,18 @@ +#!/bin/sh + +DISTRO="`grep ^ID= /etc/*-release | cut -d'"' -f2`" + +cat < -- curl 127.0.0.1:8080 + +Alternatively, to run the image directly using podman or docker, or how to use it as a parent image in a Dockerfile, see documentation at + +https://github.com/sclorg/s2i-ruby-container/blob/master/${RUBY_VERSION}/README.md +EOF diff --git a/test/examples/from-dockerfile/Dockerfile b/test/examples/from-dockerfile/Dockerfile new file mode 100644 index 0000000..3ef8baa --- /dev/null +++ b/test/examples/from-dockerfile/Dockerfile @@ -0,0 +1,7 @@ +FROM ubi8/ruby-27 + +ADD app-src ./ + +RUN bundle install --path ./bundle + +CMD exec bundle exec "rackup -P /tmp/rack.pid --host 0.0.0.0 --port 8080" diff --git a/test/examples/from-dockerfile/Dockerfile.s2i b/test/examples/from-dockerfile/Dockerfile.s2i new file mode 100644 index 0000000..fe5d456 --- /dev/null +++ b/test/examples/from-dockerfile/Dockerfile.s2i @@ -0,0 +1,15 @@ +FROM ubi8/ruby-27 + +# 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 chown -R 1001:0 /tmp/src +USER 1001 + +ENV RAILS_ENV=development +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run diff --git a/test/examples/from-dockerfile/README.md b/test/examples/from-dockerfile/README.md new file mode 100644 index 0000000..bbf5ce1 --- /dev/null +++ b/test/examples/from-dockerfile/README.md @@ -0,0 +1,21 @@ +Dockerfile examples +=================== + +This directory contains example Dockerfiles that demonstrate how to use the image with a Dockerfile and `docker build`. + +For demonstration, we use an application code available at https://github.com/sclorg/rails-ex.git. + +Pull the source to the local machine first: +``` +git clone https://github.com/sclorg/rails-ex.git app-src +``` + +Then, build a new image from a Dockerfile in this directory: +``` +docker build -f Dockerfile -t ruby-app . +``` + +And run the resulting image with the final application: +``` +docker run -ti --rm ruby-app +``` diff --git a/test/examples/rails-postgresql-persistent.json b/test/examples/rails-postgresql-persistent.json new file mode 100644 index 0000000..7accc18 --- /dev/null +++ b/test/examples/rails-postgresql-persistent.json @@ -0,0 +1,612 @@ +{ + "kind": "Template", + "apiVersion": "v1", + "metadata": { + "name": "rails-pgsql-persistent", + "annotations": { + "openshift.io/display-name": "Rails + PostgreSQL (Persistent)", + "description": "An example Rails application with a PostgreSQL database. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/rails-ex/blob/master/README.md.", + "tags": "quickstart,ruby,rails", + "iconClass": "icon-ruby", + "template.openshift.io/long-description": "This template defines resources needed to develop a Rails application, including a build configuration, application deployment configuration, and database deployment configuration.", + "template.openshift.io/provider-display-name": "Red Hat, Inc.", + "template.openshift.io/documentation-url": "https://github.com/sclorg/rails-ex", + "template.openshift.io/support-url": "https://access.redhat.com" + } + }, + "message": "The following service(s) have been created in your project: ${NAME}, ${DATABASE_SERVICE_NAME}.\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/sclorg/rails-ex/blob/master/README.md.", + "labels": { + "template": "rails-pgsql-persistent" + }, + "objects": [ + { + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "template.openshift.io/expose-username": "{.data['application-user']}", + "template.openshift.io/expose-password": "{.data['application-password']}" + } + }, + "stringData" : { + "database-user" : "${DATABASE_USER}", + "database-password" : "${DATABASE_PASSWORD}", + "application-user" : "${APPLICATION_USER}", + "application-password" : "${APPLICATION_PASSWORD}", + "keybase" : "${SECRET_KEY_BASE}" + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "description": "Exposes and load balances the application pods", + "service.alpha.openshift.io/dependencies": "[{\"name\": \"${DATABASE_SERVICE_NAME}\", \"kind\": \"Service\"}]" + } + }, + "spec": { + "ports": [ + { + "name": "web", + "port": 8080, + "targetPort": 8080 + } + ], + "selector": { + "name": "${NAME}" + } + } + }, + { + "kind": "Route", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "template.openshift.io/expose-uri": "http://{.spec.host}{.spec.path}" + } + }, + "spec": { + "host": "${APPLICATION_DOMAIN}", + "to": { + "kind": "Service", + "name": "${NAME}" + } + } + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "description": "Keeps track of changes in the application image" + } + } + }, + { + "kind": "BuildConfig", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "description": "Defines how to build the application", + "template.alpha.openshift.io/wait-for-ready": "true" + } + }, + "spec": { + "source": { + "type": "Git", + "git": { + "uri": "${SOURCE_REPOSITORY_URL}", + "ref": "${SOURCE_REPOSITORY_REF}" + }, + "contextDir": "${CONTEXT_DIR}" + }, + "strategy": { + "type": "Source", + "sourceStrategy": { + "from": { + "kind": "ImageStreamTag", + "namespace": "${NAMESPACE}", + "name": "ruby:2.5" + }, + "env": [ + { + "name": "RUBYGEM_MIRROR", + "value": "${RUBYGEM_MIRROR}" + } + ] + } + }, + "output": { + "to": { + "kind": "ImageStreamTag", + "name": "${NAME}:latest" + } + }, + "triggers": [ + { + "type": "ImageChange" + }, + { + "type": "ConfigChange" + }, + { + "type": "GitHub", + "github": { + "secret": "${GITHUB_WEBHOOK_SECRET}" + } + } + ], + "postCommit": { + "script": "bundle exec rake test" + } + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "description": "Defines how to deploy the application server", + "template.alpha.openshift.io/wait-for-ready": "true" + } + }, + "spec": { + "strategy": { + "type": "Recreate", + "recreateParams": { + "pre": { + "failurePolicy": "Abort", + "execNewPod": { + "command": [ + "./migrate-database.sh" + ], + "containerName": "${NAME}" + } + } + } + }, + "triggers": [ + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "${NAME}" + ], + "from": { + "kind": "ImageStreamTag", + "name": "${NAME}:latest" + } + } + }, + { + "type": "ConfigChange" + } + ], + "replicas": 1, + "selector": { + "name": "${NAME}" + }, + "template": { + "metadata": { + "name": "${NAME}", + "labels": { + "name": "${NAME}" + } + }, + "spec": { + "containers": [ + { + "name": "${NAME}", + "image": " ", + "ports": [ + { + "containerPort": 8080 + } + ], + "readinessProbe": { + "timeoutSeconds": 3, + "initialDelaySeconds": 5, + "httpGet": { + "path": "/articles", + "port": 8080 + } + }, + "livenessProbe": { + "timeoutSeconds": 3, + "initialDelaySeconds": 10, + "httpGet": { + "path": "/articles", + "port": 8080 + } + }, + "env": [ + { + "name": "DATABASE_SERVICE_NAME", + "value": "${DATABASE_SERVICE_NAME}" + }, + { + "name": "POSTGRESQL_USER", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-user" + } + } + }, + { + "name": "POSTGRESQL_PASSWORD", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-password" + } + } + }, + { + "name": "SECRET_KEY_BASE", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "keybase" + } + } + }, + { + "name": "POSTGRESQL_DATABASE", + "value": "${DATABASE_NAME}" + }, + { + "name": "POSTGRESQL_MAX_CONNECTIONS", + "value": "${POSTGRESQL_MAX_CONNECTIONS}" + }, + { + "name": "POSTGRESQL_SHARED_BUFFERS", + "value": "${POSTGRESQL_SHARED_BUFFERS}" + }, + { + "name": "APPLICATION_DOMAIN", + "value": "${APPLICATION_DOMAIN}" + }, + { + "name": "APPLICATION_USER", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "application-user" + } + } + }, + { + "name": "APPLICATION_PASSWORD", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "application-password" + } + } + }, + { + "name": "RAILS_ENV", + "value": "${RAILS_ENV}" + } + ], + "resources": { + "limits": { + "memory": "${MEMORY_LIMIT}" + } + } + } + ] + } + } + } + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "${DATABASE_SERVICE_NAME}" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "${VOLUME_CAPACITY}" + } + } + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "${DATABASE_SERVICE_NAME}", + "annotations": { + "description": "Exposes the database server" + } + }, + "spec": { + "ports": [ + { + "name": "postgresql", + "port": 5432, + "targetPort": 5432 + } + ], + "selector": { + "name": "${DATABASE_SERVICE_NAME}" + } + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "${DATABASE_SERVICE_NAME}", + "annotations": { + "description": "Defines how to deploy the database", + "template.alpha.openshift.io/wait-for-ready": "true" + } + }, + "spec": { + "strategy": { + "type": "Recreate" + }, + "triggers": [ + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "postgresql" + ], + "from": { + "kind": "ImageStreamTag", + "namespace": "${NAMESPACE}", + "name": "postgresql:10" + } + } + }, + { + "type": "ConfigChange" + } + ], + "replicas": 1, + "selector": { + "name": "${DATABASE_SERVICE_NAME}" + }, + "template": { + "metadata": { + "name": "${DATABASE_SERVICE_NAME}", + "labels": { + "name": "${DATABASE_SERVICE_NAME}" + } + }, + "spec": { + "volumes": [ + { + "name": "${DATABASE_SERVICE_NAME}-data", + "persistentVolumeClaim": { + "claimName": "${DATABASE_SERVICE_NAME}" + } + } + ], + "containers": [ + { + "name": "postgresql", + "image": " ", + "ports": [ + { + "containerPort": 5432 + } + ], + "readinessProbe": { + "timeoutSeconds": 1, + "initialDelaySeconds": 5, + "exec": { + "command": [ "/bin/sh", "-i", "-c", "psql -h 127.0.0.1 -U ${POSTGRESQL_USER} -q -d ${POSTGRESQL_DATABASE} -c 'SELECT 1'"] + } + }, + "livenessProbe": { + "timeoutSeconds": 1, + "initialDelaySeconds": 30, + "tcpSocket": { + "port": 5432 + } + }, + "volumeMounts": [ + { + "name": "${DATABASE_SERVICE_NAME}-data", + "mountPath": "/var/lib/pgsql/data" + } + ], + "env": [ + { + "name": "POSTGRESQL_USER", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-user" + } + } + }, + { + "name": "POSTGRESQL_PASSWORD", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-password" + } + } + }, + { + "name": "POSTGRESQL_DATABASE", + "value": "${DATABASE_NAME}" + }, + { + "name": "POSTGRESQL_MAX_CONNECTIONS", + "value": "${POSTGRESQL_MAX_CONNECTIONS}" + }, + { + "name": "POSTGRESQL_SHARED_BUFFERS", + "value": "${POSTGRESQL_SHARED_BUFFERS}" + } + ], + "resources": { + "limits": { + "memory": "${MEMORY_POSTGRESQL_LIMIT}" + } + } + } + ] + } + } + } + } + ], + "parameters": [ + { + "name": "NAME", + "displayName": "Name", + "description": "The name assigned to all of the frontend objects defined in this template.", + "required": true, + "value": "rails-pgsql-persistent" + }, + { + "name": "NAMESPACE", + "displayName": "Namespace", + "required": true, + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { + "name": "MEMORY_LIMIT", + "displayName": "Memory Limit", + "required": true, + "description": "Maximum amount of memory the Rails container can use.", + "value": "512Mi" + }, + { + "name": "MEMORY_POSTGRESQL_LIMIT", + "displayName": "Memory Limit (PostgreSQL)", + "required": true, + "description": "Maximum amount of memory the PostgreSQL container can use.", + "value": "512Mi" + }, + { + "name": "VOLUME_CAPACITY", + "displayName": "Volume Capacity", + "description": "Volume space available for data, e.g. 512Mi, 2Gi", + "value": "1Gi", + "required": true + }, + { + "name": "SOURCE_REPOSITORY_URL", + "displayName": "Git Repository URL", + "required": true, + "description": "The URL of the repository with your application source code.", + "value": "https://github.com/sclorg/rails-ex.git" + }, + { + "name": "SOURCE_REPOSITORY_REF", + "displayName": "Git Reference", + "description": "Set this to a branch name, tag or other ref of your repository if you are not using the default branch." + }, + { + "name": "CONTEXT_DIR", + "displayName": "Context Directory", + "description": "Set this to the relative path to your project if it is not in the root of your repository." + }, + { + "name": "APPLICATION_DOMAIN", + "displayName": "Application Hostname", + "description": "The exposed hostname that will route to the Rails service, if left blank a value will be defaulted.", + "value": "" + }, + { + "name": "GITHUB_WEBHOOK_SECRET", + "displayName": "GitHub Webhook Secret", + "description": "Github trigger secret. A difficult to guess string encoded as part of the webhook URL. Not encrypted.", + "generate": "expression", + "from": "[a-zA-Z0-9]{40}" + }, + { + "name": "SECRET_KEY_BASE", + "displayName": "Secret Key", + "description": "Your secret key for verifying the integrity of signed cookies.", + "generate": "expression", + "from": "[a-z0-9]{127}" + }, + { + "name": "APPLICATION_USER", + "displayName": "Application Username", + "required": true, + "description": "The application user that is used within the sample application to authorize access on pages.", + "value": "openshift" + }, + { + "name": "APPLICATION_PASSWORD", + "displayName": "Application Password", + "required": true, + "description": "The application password that is used within the sample application to authorize access on pages.", + "value": "secret" + }, + { + "name": "RAILS_ENV", + "displayName": "Rails Environment", + "required": true, + "description": "Environment under which the sample application will run. Could be set to production, development or test.", + "value": "production" + }, + { + "name": "DATABASE_SERVICE_NAME", + "required": true, + "displayName": "Database Service Name", + "value": "postgresql" + }, + { + "name": "DATABASE_USER", + "displayName": "Database Username", + "generate": "expression", + "from": "user[A-Z0-9]{3}" + }, + { + "name": "DATABASE_PASSWORD", + "displayName": "Database Password", + "generate": "expression", + "from": "[a-zA-Z0-9]{8}" + }, + { + "name": "DATABASE_NAME", + "required": true, + "displayName": "Database Name", + "value": "root" + }, + { + "name": "POSTGRESQL_MAX_CONNECTIONS", + "displayName": "Maximum Database Connections", + "value": "100" + }, + { + "name": "POSTGRESQL_SHARED_BUFFERS", + "displayName": "Shared Buffer Amount", + "value": "12MB" + }, + { + "name": "RUBYGEM_MIRROR", + "displayName": "Custom RubyGems Mirror URL", + "description": "The custom RubyGems mirror URL", + "value": "" + } + ] +} diff --git a/test/examples/rails-postgresql.json b/test/examples/rails-postgresql.json new file mode 100644 index 0000000..d444fae --- /dev/null +++ b/test/examples/rails-postgresql.json @@ -0,0 +1,586 @@ +{ + "kind": "Template", + "apiVersion": "v1", + "metadata": { + "name": "rails-postgresql-example", + "annotations": { + "openshift.io/display-name": "Rails + PostgreSQL (Ephemeral)", + "description": "An example Rails application with a PostgreSQL database. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/rails-ex/blob/master/README.md.\n\nWARNING: Any data stored will be lost upon pod destruction. Only use this template for testing.", + "tags": "quickstart,ruby,rails", + "iconClass": "icon-ruby", + "template.openshift.io/long-description": "This template defines resources needed to develop a Rails application, including a build configuration, application deployment configuration, and database deployment configuration. The database is stored in non-persistent storage, so this configuration should be used for experimental purposes only.", + "template.openshift.io/provider-display-name": "Red Hat, Inc.", + "template.openshift.io/documentation-url": "https://github.com/sclorg/rails-ex", + "template.openshift.io/support-url": "https://access.redhat.com" + } + }, + "message": "The following service(s) have been created in your project: ${NAME}, ${DATABASE_SERVICE_NAME}.\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/sclorg/rails-ex/blob/master/README.md.", + "labels": { + "template": "rails-postgresql-example" + }, + "objects": [ + { + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "template.openshift.io/expose-username": "{.data['application-user']}", + "template.openshift.io/expose-password": "{.data['application-password']}" + } + }, + "stringData" : { + "database-user" : "${DATABASE_USER}", + "database-password" : "${DATABASE_PASSWORD}", + "application-user" : "${APPLICATION_USER}", + "application-password" : "${APPLICATION_PASSWORD}", + "keybase" : "${SECRET_KEY_BASE}" + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "description": "Exposes and load balances the application pods", + "service.alpha.openshift.io/dependencies": "[{\"name\": \"${DATABASE_SERVICE_NAME}\", \"kind\": \"Service\"}]" + } + }, + "spec": { + "ports": [ + { + "name": "web", + "port": 8080, + "targetPort": 8080 + } + ], + "selector": { + "name": "${NAME}" + } + } + }, + { + "kind": "Route", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "template.openshift.io/expose-uri": "http://{.spec.host}{.spec.path}" + } + }, + "spec": { + "host": "${APPLICATION_DOMAIN}", + "to": { + "kind": "Service", + "name": "${NAME}" + } + } + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "description": "Keeps track of changes in the application image" + } + } + }, + { + "kind": "BuildConfig", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "description": "Defines how to build the application", + "template.alpha.openshift.io/wait-for-ready": "true" + } + }, + "spec": { + "source": { + "type": "Git", + "git": { + "uri": "${SOURCE_REPOSITORY_URL}", + "ref": "${SOURCE_REPOSITORY_REF}" + }, + "contextDir": "${CONTEXT_DIR}" + }, + "strategy": { + "type": "Source", + "sourceStrategy": { + "from": { + "kind": "ImageStreamTag", + "namespace": "${NAMESPACE}", + "name": "ruby:2.5" + }, + "env": [ + { + "name": "RUBYGEM_MIRROR", + "value": "${RUBYGEM_MIRROR}" + } + ] + } + }, + "output": { + "to": { + "kind": "ImageStreamTag", + "name": "${NAME}:latest" + } + }, + "triggers": [ + { + "type": "ImageChange" + }, + { + "type": "ConfigChange" + }, + { + "type": "GitHub", + "github": { + "secret": "${GITHUB_WEBHOOK_SECRET}" + } + } + ], + "postCommit": { + "script": "bundle exec rake test" + } + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}", + "annotations": { + "description": "Defines how to deploy the application server", + "template.alpha.openshift.io/wait-for-ready": "true" + } + }, + "spec": { + "strategy": { + "type": "Recreate", + "recreateParams": { + "pre": { + "failurePolicy": "Abort", + "execNewPod": { + "command": [ + "./migrate-database.sh" + ], + "containerName": "${NAME}" + } + } + } + }, + "triggers": [ + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "${NAME}" + ], + "from": { + "kind": "ImageStreamTag", + "name": "${NAME}:latest" + } + } + }, + { + "type": "ConfigChange" + } + ], + "replicas": 1, + "selector": { + "name": "${NAME}" + }, + "template": { + "metadata": { + "name": "${NAME}", + "labels": { + "name": "${NAME}" + } + }, + "spec": { + "containers": [ + { + "name": "${NAME}", + "image": " ", + "ports": [ + { + "containerPort": 8080 + } + ], + "readinessProbe": { + "timeoutSeconds": 3, + "initialDelaySeconds": 5, + "httpGet": { + "path": "/articles", + "port": 8080 + } + }, + "livenessProbe": { + "timeoutSeconds": 3, + "initialDelaySeconds": 10, + "httpGet": { + "path": "/articles", + "port": 8080 + } + }, + "env": [ + { + "name": "DATABASE_SERVICE_NAME", + "value": "${DATABASE_SERVICE_NAME}" + }, + { + "name": "POSTGRESQL_USER", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-user" + } + } + }, + { + "name": "POSTGRESQL_PASSWORD", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-password" + } + } + }, + { + "name": "POSTGRESQL_DATABASE", + "value": "${DATABASE_NAME}" + }, + { + "name": "SECRET_KEY_BASE", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "keybase" + } + } + }, + { + "name": "POSTGRESQL_MAX_CONNECTIONS", + "value": "${POSTGRESQL_MAX_CONNECTIONS}" + }, + { + "name": "POSTGRESQL_SHARED_BUFFERS", + "value": "${POSTGRESQL_SHARED_BUFFERS}" + }, + { + "name": "APPLICATION_DOMAIN", + "value": "${APPLICATION_DOMAIN}" + }, + { + "name": "APPLICATION_USER", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "application-user" + } + } + }, + { + "name": "APPLICATION_PASSWORD", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "application-password" + } + } + }, + { + "name": "RAILS_ENV", + "value": "${RAILS_ENV}" + } + ], + "resources": { + "limits": { + "memory": "${MEMORY_LIMIT}" + } + } + } + ] + } + } + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "${DATABASE_SERVICE_NAME}", + "annotations": { + "description": "Exposes the database server" + } + }, + "spec": { + "ports": [ + { + "name": "postgresql", + "port": 5432, + "targetPort": 5432 + } + ], + "selector": { + "name": "${DATABASE_SERVICE_NAME}" + } + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "${DATABASE_SERVICE_NAME}", + "annotations": { + "description": "Defines how to deploy the database", + "template.alpha.openshift.io/wait-for-ready": "true" + } + }, + "spec": { + "strategy": { + "type": "Recreate" + }, + "triggers": [ + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "postgresql" + ], + "from": { + "kind": "ImageStreamTag", + "namespace": "${NAMESPACE}", + "name": "postgresql:10" + } + } + }, + { + "type": "ConfigChange" + } + ], + "replicas": 1, + "selector": { + "name": "${DATABASE_SERVICE_NAME}" + }, + "template": { + "metadata": { + "name": "${DATABASE_SERVICE_NAME}", + "labels": { + "name": "${DATABASE_SERVICE_NAME}" + } + }, + "spec": { + "volumes": [ + { + "name": "data", + "emptyDir": {} + } + ], + "containers": [ + { + "name": "postgresql", + "image": " ", + "ports": [ + { + "containerPort": 5432 + } + ], + "readinessProbe": { + "timeoutSeconds": 1, + "initialDelaySeconds": 5, + "exec": { + "command": [ "/bin/sh", "-i", "-c", "psql -h 127.0.0.1 -U ${POSTGRESQL_USER} -q -d ${POSTGRESQL_DATABASE} -c 'SELECT 1'"] + } + }, + "livenessProbe": { + "timeoutSeconds": 1, + "initialDelaySeconds": 30, + "tcpSocket": { + "port": 5432 + } + }, + "volumeMounts": [ + { + "name": "data", + "mountPath": "/var/lib/pgsql/data" + } + ], + "env": [ + { + "name": "POSTGRESQL_USER", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-user" + } + } + }, + { + "name": "POSTGRESQL_PASSWORD", + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-password" + } + } + }, + { + "name": "POSTGRESQL_DATABASE", + "value": "${DATABASE_NAME}" + }, + { + "name": "POSTGRESQL_MAX_CONNECTIONS", + "value": "${POSTGRESQL_MAX_CONNECTIONS}" + }, + { + "name": "POSTGRESQL_SHARED_BUFFERS", + "value": "${POSTGRESQL_SHARED_BUFFERS}" + } + ], + "resources": { + "limits": { + "memory": "${MEMORY_POSTGRESQL_LIMIT}" + } + } + } + ] + } + } + } + } + ], + "parameters": [ + { + "name": "NAME", + "displayName": "Name", + "description": "The name assigned to all of the frontend objects defined in this template.", + "required": true, + "value": "rails-postgresql-example" + }, + { + "name": "NAMESPACE", + "displayName": "Namespace", + "required": true, + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { + "name": "MEMORY_LIMIT", + "displayName": "Memory Limit", + "required": true, + "description": "Maximum amount of memory the Rails container can use.", + "value": "512Mi" + }, + { + "name": "MEMORY_POSTGRESQL_LIMIT", + "displayName": "Memory Limit (PostgreSQL)", + "required": true, + "description": "Maximum amount of memory the PostgreSQL container can use.", + "value": "512Mi" + }, + { + "name": "SOURCE_REPOSITORY_URL", + "displayName": "Git Repository URL", + "required": true, + "description": "The URL of the repository with your application source code.", + "value": "https://github.com/sclorg/rails-ex.git" + }, + { + "name": "SOURCE_REPOSITORY_REF", + "displayName": "Git Reference", + "description": "Set this to a branch name, tag or other ref of your repository if you are not using the default branch." + }, + { + "name": "CONTEXT_DIR", + "displayName": "Context Directory", + "description": "Set this to the relative path to your project if it is not in the root of your repository." + }, + { + "name": "APPLICATION_DOMAIN", + "displayName": "Application Hostname", + "description": "The exposed hostname that will route to the Rails service, if left blank a value will be defaulted.", + "value": "" + }, + { + "name": "GITHUB_WEBHOOK_SECRET", + "displayName": "GitHub Webhook Secret", + "description": "Github trigger secret. A difficult to guess string encoded as part of the webhook URL. Not encrypted.", + "generate": "expression", + "from": "[a-zA-Z0-9]{40}" + }, + { + "name": "SECRET_KEY_BASE", + "displayName": "Secret Key", + "description": "Your secret key for verifying the integrity of signed cookies.", + "generate": "expression", + "from": "[a-z0-9]{127}" + }, + { + "name": "APPLICATION_USER", + "displayName": "Application Username", + "required": true, + "description": "The application user that is used within the sample application to authorize access on pages.", + "value": "openshift" + }, + { + "name": "APPLICATION_PASSWORD", + "displayName": "Application Password", + "required": true, + "description": "The application password that is used within the sample application to authorize access on pages.", + "value": "secret" + }, + { + "name": "RAILS_ENV", + "displayName": "Rails Environment", + "required": true, + "description": "Environment under which the sample application will run. Could be set to production, development or test.", + "value": "production" + }, + { + "name": "DATABASE_SERVICE_NAME", + "required": true, + "displayName": "Database Service Name", + "value": "postgresql" + }, + { + "name": "DATABASE_USER", + "displayName": "Database Username", + "generate": "expression", + "from": "user[A-Z0-9]{3}" + }, + { + "name": "DATABASE_PASSWORD", + "displayName": "Database Password", + "generate": "expression", + "from": "[a-zA-Z0-9]{8}" + }, + { + "name": "DATABASE_NAME", + "required": true, + "displayName": "Database Name", + "value": "root" + }, + { + "name": "POSTGRESQL_MAX_CONNECTIONS", + "displayName": "Maximum Database Connections", + "value": "100" + }, + { + "name": "POSTGRESQL_SHARED_BUFFERS", + "displayName": "Shared Buffer Amount", + "value": "12MB" + }, + { + "name": "RUBYGEM_MIRROR", + "displayName": "Custom RubyGems Mirror URL", + "description": "The custom RubyGems mirror URL", + "value": "" + } + ] +} diff --git a/test/imagestreams/ruby-centos.json b/test/imagestreams/ruby-centos.json new file mode 100644 index 0000000..c3b6d3f --- /dev/null +++ b/test/imagestreams/ruby-centos.json @@ -0,0 +1,192 @@ +{ + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "ruby", + "annotations": { + "openshift.io/display-name": "Ruby" + } + }, + "spec": { + "tags": [ + { + "name": "latest", + "annotations": { + "openshift.io/display-name": "Ruby (Latest)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby applications on UBI. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/tree/master/2.6/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Ruby available on OpenShift, including major version updates.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "ImageStreamTag", + "name": "2.7-ubi8" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.7-ubi8", + "annotations": { + "openshift.io/display-name": "Ruby 2.7 (UBI 8)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.7 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.7/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.7,ruby", + "version": "2.7", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.access.redhat.com/ubi8/ruby-27:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "3.0-ubi7", + "annotations": { + "openshift.io/display-name": "Ruby 3.0 (UBI 7)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 3.0 applications on UBI 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/3.0/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:3.0,ruby", + "version": "3.0", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.access.redhat.com/ubi7/ruby-30:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.7-ubi7", + "annotations": { + "openshift.io/display-name": "Ruby 2.7 (UBI 7)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.7 applications on UBI 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.7/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.7,ruby", + "version": "2.7", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.access.redhat.com/ubi7/ruby-27:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.7", + "annotations": { + "openshift.io/display-name": "Ruby 2.7", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/tree/master/2.7/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Ruby available on OpenShift, including major version updates.", + "iconClass": "icon-ruby", + "tags": "builder,ruby,hidden", + "supports": "ruby", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "quay.io/centos7/ruby-27-centos7:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.6-ubi8", + "annotations": { + "openshift.io/display-name": "Ruby 2.6 (UBI 8)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.6 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.6/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.6,ruby", + "version": "2.6", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.access.redhat.com/ubi8/ruby-26:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.6-ubi7", + "annotations": { + "openshift.io/display-name": "Ruby 2.6 (UBI 7)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.6 applications on UBI 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.6/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.6,ruby", + "version": "2.6", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.access.redhat.com/ubi7/ruby-26:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.6", + "annotations": { + "openshift.io/display-name": "Ruby 2.6", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.6 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.6/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby,hidden", + "supports": "ruby:2.6,ruby", + "version": "2.6", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "quay.io/centos7/ruby-26-centos7:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.5-ubi8", + "annotations": { + "openshift.io/display-name": "Ruby 2.5 (UBI 8)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.5 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.5/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.5,ruby", + "version": "2.5", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.access.redhat.com/ubi8/ruby-25:latest" + }, + "referencePolicy": { + "type": "Local" + } + } + ] + } +} diff --git a/test/imagestreams/ruby-rhel-aarch64.json b/test/imagestreams/ruby-rhel-aarch64.json new file mode 100644 index 0000000..fe72d0d --- /dev/null +++ b/test/imagestreams/ruby-rhel-aarch64.json @@ -0,0 +1,93 @@ +{ + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "ruby", + "annotations": { + "openshift.io/display-name": "Ruby" + } + }, + "spec": { + "tags": [ + { + "name": "latest", + "annotations": { + "openshift.io/display-name": "Ruby (Latest)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby applications on UBI. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/tree/master/2.7/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Ruby available on OpenShift, including major version updates.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "ImageStreamTag", + "name": "2.7-ubi8" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.7-ubi8", + "annotations": { + "openshift.io/display-name": "Ruby 2.7 (UBI 8)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.7 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.7/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.7,ruby", + "version": "2.7", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi8/ruby-27:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.6-ubi8", + "annotations": { + "openshift.io/display-name": "Ruby 2.6 (UBI 8)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.6 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.6/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.6,ruby", + "version": "2.6", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi8/ruby-26:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.5-ubi8", + "annotations": { + "openshift.io/display-name": "Ruby 2.5 (UBI 8)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.5 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.5/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.5,ruby", + "version": "2.5", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi8/ruby-25:latest" + }, + "referencePolicy": { + "type": "Local" + } + } + ] + } +} diff --git a/test/imagestreams/ruby-rhel.json b/test/imagestreams/ruby-rhel.json new file mode 100644 index 0000000..73e0b91 --- /dev/null +++ b/test/imagestreams/ruby-rhel.json @@ -0,0 +1,193 @@ +{ + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "ruby", + "annotations": { + "openshift.io/display-name": "Ruby" + } + }, + "spec": { + "tags": [ + { + "name": "latest", + "annotations": { + "openshift.io/display-name": "Ruby (Latest)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby applications on UBI. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/tree/master/2.7/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Ruby available on OpenShift, including major version updates.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "ImageStreamTag", + "name": "2.7-ubi8" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.7-ubi8", + "annotations": { + "openshift.io/display-name": "Ruby 2.7 (UBI 8)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.7 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.7/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.7,ruby", + "version": "2.7", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi8/ruby-27:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "3.0-ubi7", + "annotations": { + "openshift.io/display-name": "Ruby 3.0 (UBI 7)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 3.0 applications on UBI 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/3.0/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:3.0,ruby", + "version": "3.0", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi7/ruby-30:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.7-ubi7", + "annotations": { + "openshift.io/display-name": "Ruby 2.7 (UBI 7)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.7 applications on UBI 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.7/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.7,ruby", + "version": "2.7", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi7/ruby-27:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.7", + "annotations": { + "openshift.io/display-name": "Ruby 2.7", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.7 applications on RHEL 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.7/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby,hidden", + "supports": "ruby:2.7,ruby", + "version": "2.7", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/rhscl/ruby-27-rhel7:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.6-ubi8", + "annotations": { + "openshift.io/display-name": "Ruby 2.6 (UBI 8)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.6 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.6/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.6,ruby", + "version": "2.6", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi8/ruby-26:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.6-ubi7", + "annotations": { + "openshift.io/display-name": "Ruby 2.6 (UBI 7)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.6 applications on UBI 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.6/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.6,ruby", + "version": "2.6", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi7/ruby-26:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.6", + "annotations": { + "openshift.io/display-name": "Ruby 2.6", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.6 applications on RHEL 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.6/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby,hidden", + "supports": "ruby:2.6,ruby", + "version": "2.6", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/rhscl/ruby-26-rhel7:latest" + }, + "referencePolicy": { + "type": "Local" + } + }, + { + "name": "2.5-ubi8", + "annotations": { + "openshift.io/display-name": "Ruby 2.5 (UBI 8)", + "openshift.io/provider-display-name": "Red Hat, Inc.", + "description": "Build and run Ruby 2.5 applications on UBI 8. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.5/README.md.", + "iconClass": "icon-ruby", + "tags": "builder,ruby", + "supports": "ruby:2.5,ruby", + "version": "2.5", + "sampleRepo": "https://github.com/sclorg/ruby-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "registry.redhat.io/ubi8/ruby-25:latest" + }, + "referencePolicy": { + "type": "Local" + } + } + ] + } +} diff --git a/test/puma-test-app/Gemfile b/test/puma-test-app/Gemfile new file mode 100755 index 0000000..a98e5e1 --- /dev/null +++ b/test/puma-test-app/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'sinatra' +gem 'puma' diff --git a/test/puma-test-app/Gemfile.lock b/test/puma-test-app/Gemfile.lock new file mode 100644 index 0000000..2b2190d --- /dev/null +++ b/test/puma-test-app/Gemfile.lock @@ -0,0 +1,21 @@ +GEM + remote: https://rubygems.org/ + specs: + mustermann (1.0.1) + puma (3.11.2) + rack (2.0.4) + rack-protection (2.0.0) + rack + sinatra (2.0.0) + mustermann (~> 1.0) + rack (~> 2.0) + rack-protection (= 2.0.0) + tilt (~> 2.0) + tilt (2.0.8) + +PLATFORMS + ruby + +DEPENDENCIES + puma + sinatra diff --git a/test/puma-test-app/app.rb b/test/puma-test-app/app.rb new file mode 100755 index 0000000..cd7e87d --- /dev/null +++ b/test/puma-test-app/app.rb @@ -0,0 +1,5 @@ +require 'sinatra' + +get '/' do + 'Hello world!' +end diff --git a/test/puma-test-app/config.ru b/test/puma-test-app/config.ru new file mode 100755 index 0000000..76a6edf --- /dev/null +++ b/test/puma-test-app/config.ru @@ -0,0 +1,2 @@ +require './app' +run Sinatra::Application diff --git a/test/rack-test-app/Gemfile b/test/rack-test-app/Gemfile new file mode 100755 index 0000000..c10abd7 --- /dev/null +++ b/test/rack-test-app/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'sinatra' diff --git a/test/rack-test-app/Gemfile.lock b/test/rack-test-app/Gemfile.lock new file mode 100644 index 0000000..1569406 --- /dev/null +++ b/test/rack-test-app/Gemfile.lock @@ -0,0 +1,19 @@ +GEM + remote: https://rubygems.org/ + specs: + mustermann (1.0.1) + rack (2.0.4) + rack-protection (2.0.0) + rack + sinatra (2.0.0) + mustermann (~> 1.0) + rack (~> 2.0) + rack-protection (= 2.0.0) + tilt (~> 2.0) + tilt (2.0.8) + +PLATFORMS + ruby + +DEPENDENCIES + sinatra diff --git a/test/rack-test-app/app.rb b/test/rack-test-app/app.rb new file mode 100755 index 0000000..cd7e87d --- /dev/null +++ b/test/rack-test-app/app.rb @@ -0,0 +1,5 @@ +require 'sinatra' + +get '/' do + 'Hello world!' +end diff --git a/test/rack-test-app/config.ru b/test/rack-test-app/config.ru new file mode 100755 index 0000000..76a6edf --- /dev/null +++ b/test/rack-test-app/config.ru @@ -0,0 +1,2 @@ +require './app' +run Sinatra::Application diff --git a/test/run b/test/run new file mode 100755 index 0000000..4e635b5 --- /dev/null +++ b/test/run @@ -0,0 +1,269 @@ +#!/bin/bash -x +# +# The 'run' performs a simple test that verifies that S2I image. +# The main focus here is to excersise the S2I scripts. +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# +IMAGE_NAME=${IMAGE_NAME-quay.io/centos7/ruby-27-centos7-candidate} + +declare -a WEB_SERVERS=(db puma rack) +#declare -a WEB_SERVERS=(db) + +# TODO: Make command compatible for Mac users +test_dir="$(readlink -zf $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -zf ${test_dir}/..) +test_short_summary='' +TESTSUITE_RESULT=0 + +TEST_LIST="\ +test_docker_run_usage +test_application +test_connection +test_scl_variables_in_dockerfile +test_scl_usage +test_npm_functionality +" +source "${test_dir}/test-lib.sh" + +# Read exposed port from image meta data +test_port="$(docker inspect --format='{{range $key, $value := .Config.ExposedPorts }}{{$key}}{{end}}' ${IMAGE_NAME} | sed 's/\/.*//')" + +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() { + ct_s2i_build_as_df file://${test_dir}/${1}-test-app ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} $(ct_build_s2i_npm_variables) +} + +run_test_application() { + docker run --user=100001 --rm --cidfile=${cid_file} ${IMAGE_NAME}-testapp +} + +cleanup() { + local server="$1" + info "Cleaning up the test application image $1" + if [ -f $cid_file ]; then + if container_exists; then + docker stop $(cat $cid_file) + fi + fi + if image_exists ${IMAGE_NAME}-testapp; then + docker rmi -f ${IMAGE_NAME}-testapp + fi + if [ ! -z "${server}" ]; then + rm -rf ${test_dir}/${server}-test-app/.git + fi + if [[ "${server}" == "db" ]]; then + rm -rf ${test_dir}/db-test-app + fi +} + +check_result() { + local result="$1" + if [[ "$result" != "0" ]]; then + TESTCASE_RESULT=1 + fi + return $result +} + +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + local result=1 + info "Waiting for application container to start" + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && break + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + if [ $attempt -gt $max_attempts ]; then + return 1 + fi +} + +test_s2i_usage() { + info "Testing 's2i usage'" + ct_s2i_usage ${IMAGE_NAME} ${s2i_args} &>/dev/null + check_result "$?" +} + +test_docker_run_usage() { + info "Testing 'docker run' usage" + docker run ${IMAGE_NAME} &>/dev/null + check_result "$?" +} + +test_connection() { + info "Testing the HTTP connection (http://$(container_ip):${test_port})" + local max_attempts=10 + 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 + echo "SUCCESS, container returned 200" + result=0 + fi + break + fi + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return $result +} + +scl_usage() { + local run_cmd="$1" + local expected="$2" + + info "Testing the image SCL enable" + out=$(docker run --rm ${IMAGE_NAME} /bin/bash -c "${run_cmd}") + 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 + return 0 +} + +function test_scl_usage() { + scl_usage "ruby --version" "ruby ${RUBY_VERSION}." + check_result "$?" +} + +function test_npm_functionality() { + info "Testing npm availibility" + ct_npm_works + check_result "$?" +} + +function handle_test_case_result() { + local output_format + output_format="%s %s $1\n" + shift + local test_msg + if [ $TESTCASE_RESULT -eq 0 ]; then + test_msg="[PASSED]" + else + test_msg="[FAILED]" + TESTSUITE_RESULT=1 + fi + printf -v test_short_summary "${output_format}" "${test_short_summary}" "${test_msg}" $@ +} + +function run_all_tests() { + local APP_NAME="$1" + for test_case in $TEST_SET; do + info "Running test $test_case ... " + TESTCASE_RESULT=0 + $test_case + check_result $? + local test_msg + handle_test_case_result "for '%s' %s" "${APP_NAME}" "$test_case" + [ -n "${FAIL_QUICKLY:-}" ] && { + cleanup "${APP_NAME}" + return 1 + } + done; + + cleanup "${APP_NAME}" +} + +function test_application() { + # 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 + check_result $? +} + +function test_scl_variables_in_dockerfile() { + if [ "$OS" == "rhel7" ] || [ "$OS" == "centos7" ]; then + # autocleanup only enabled here as only the following tests so far use it + CID_FILE_DIR=$(mktemp -d) + ct_enable_cleanup + + info "Testing variable presence during \`docker exec\`" + ct_check_exec_env_vars + check_result $? + + info "Checking if all scl variables are defined in Dockerfile" + ct_check_scl_enable_vars + check_result $? + fi +} + +function test_from_dockerfile() { + dockerfile="Dockerfile${1:-}" + TESTCASE_RESULT=0 + info "Check building using a $dockerfile" + ct_test_app_dockerfile $test_dir/examples/from-dockerfile/$dockerfile 'https://github.com/sclorg/rails-ex.git' 'Welcome to your Rails application on OpenShift' app-src + check_result $? + handle_test_case_result "test_from_dockerfile for %s" "${dockerfile}" +} + +pushd ${test_dir} +if [ -d db-test-app ]; then + rm -rf db-test-app +fi +git clone git://github.com/openshift/ruby-hello-world db-test-app +popd + +for server in ${WEB_SERVERS[@]}; do + cid_file=$(mktemp -u --suffix=.cid) + + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + TESTCASE_RESULT=0 + run_s2i_build "${server}" + check_result $? + handle_test_case_result "build of '%s' app" "${server}" + + TEST_SET=${TESTS:-$TEST_LIST} run_all_tests "$server" + + cleanup "${server}" +done + +test_from_dockerfile +test_from_dockerfile ".s2i" + +echo "$test_short_summary" + +if [ $TESTSUITE_RESULT -eq 0 ] ; then + echo "Tests for ${IMAGE_NAME} succeeded." +else + echo "Tests for ${IMAGE_NAME} failed." +fi + +exit $TESTSUITE_RESULT diff --git a/test/run-conu b/test/run-conu new file mode 100755 index 0000000..4812aed --- /dev/null +++ b/test/run-conu @@ -0,0 +1,2 @@ +#!/bin/bash +exec pytest -vv -k test_s2i diff --git a/test/run-openshift b/test/run-openshift new file mode 120000 index 0000000..d84575f --- /dev/null +++ b/test/run-openshift @@ -0,0 +1 @@ +run-openshift-local-cluster \ No newline at end of file diff --git a/test/run-openshift-remote-cluster b/test/run-openshift-remote-cluster new file mode 100755 index 0000000..8488062 --- /dev/null +++ b/test/run-openshift-remote-cluster @@ -0,0 +1,37 @@ +#!/bin/bash +# +# Test the Ruby S2I image in OpenShift (remote cluster) +# +# 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 Ruby runtime in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel7) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +source ${THISDIR}/test-lib-ruby.sh +source ${THISDIR}/test-lib-remote-openshift.sh + +set -eo nounset + +ct_os_set_ocp4 + +trap ct_os_cleanup EXIT SIGINT + +ct_os_check_compulsory_vars + +oc status || false "It looks like oc is not properly logged in." + +# For testing on OpenShift 4 we use internal registry +export CT_EXTERNAL_REGISTRY=true + +test_ruby_integration "${IMAGE_NAME}" + +# Check the imagestream +test_ruby_imagestream + +OS_TESTSUITE_RESULT=0 + +# vim: set tabstop=2:shiftwidth=2:expandtab: + diff --git a/test/test-lib-openshift.sh b/test/test-lib-openshift.sh new file mode 120000 index 0000000..f48003b --- /dev/null +++ b/test/test-lib-openshift.sh @@ -0,0 +1 @@ +../common/test-lib-openshift.sh \ No newline at end of file diff --git a/test/test-lib-remote-openshift.sh b/test/test-lib-remote-openshift.sh new file mode 100644 index 0000000..bda03f6 --- /dev/null +++ b/test/test-lib-remote-openshift.sh @@ -0,0 +1,116 @@ +# shellcheck shell=bash +# some functions are used from test-lib.sh, that is usually in the same dir +# shellcheck source=/dev/null +source "$(dirname "${BASH_SOURCE[0]}")"/test-lib.sh + +# Set of functions for testing docker images in OpenShift using 'oc' command + +# A variable containing the overall test result; must be changed to 0 in the end +# of the testing script: +# OS_TESTSUITE_RESULT=0 +# And the following trap must be set, in the beginning of the test script: +# trap ct_os_cleanup EXIT SIGINT + +# ct_os_set_path_oc_4 OC_VERSION +# -------------------- +# This is a trick that helps using correct version 4 of the `oc`: +# The input is version of the openshift in format 4.4 etc. +# If the currently available version of oc is not of this version, +# it first takes a look into /usr/local/oc-/bin directory, + +# Arguments: oc_version - X.Y part of the version of OSE (e.g. 3.9) +function ct_os_set_path_oc_4() { + echo "Setting OCP4 client" + local oc_version=$1 + local installed_oc_path="/usr/local/oc-v${oc_version}/bin" + echo "PATH ${installed_oc_path}" + if [ -x "${installed_oc_path}/oc" ] ; then + oc_path="${installed_oc_path}" + echo "Binary oc found in ${installed_oc_path}" >&2 + else + echo "OCP4 not found" + return 1 + fi + export PATH="${oc_path}:${PATH}" + oc version + if ! oc version | grep -q "Client Version: ${oc_version}." ; then + echo "ERROR: something went wrong, oc located at ${oc_path}, but oc of version ${oc_version} not found in PATH ($PATH)" >&1 + return 1 + else + echo "PATH set correctly, binary oc found in version ${oc_version}: $(command -v oc)" + fi +} + +# ct_os_prepare_ocp4 +# ------------------ +# Prepares environment for testing images in OpenShift 4 environment +# +# +function ct_os_set_ocp4() { + if [ "${CVP:-0}" -eq "1" ]; then + echo "Testing in CVP environment. No need to login to OpenShift cluster. This is already done by CVP pipeline." + return + fi + local login + OS_OC_CLIENT_VERSION=${OS_OC_CLIENT_VERSION:-4.4} + ct_os_set_path_oc_4 "${OS_OC_CLIENT_VERSION}" + + oc version + + login=$(cat "$KUBEPASSWORD") + oc login -u kubeadmin -p "$login" + echo "Login to OpenShift ${OS_OC_CLIENT_VERSION} is DONE" + # let openshift cluster to sync to avoid some race condition errors + sleep 3 +} + +function ct_os_upload_image_external_registry() { + local input_name="${1}" ; shift + local image_name=${input_name##*/} + local imagestream=${1:-$image_name:latest} + local output_name + + ct_os_login_external_registry + + output_name="${INTERNAL_DOCKER_REGISTRY}/rhscl-ci-testing/$imagestream" + + docker images + docker tag "${input_name}" "${output_name}" + docker push "${output_name}" +} + + +function ct_os_login_external_registry() { + local docker_token + # docker login fails with "404 page not found" error sometimes, just try it more times + # shellcheck disable=SC2034 + echo "loging" + [ -z "${INTERNAL_DOCKER_REGISTRY:-}" ] && "INTERNAL_DOCKER_REGISTRY has to be set for working with Internal registry" && return 1 + # shellcheck disable=SC2034 + for i in $(seq 12) ; do + # shellcheck disable=SC2015 + docker_token=$(cat "$DOCKER_UPSHIFT_TOKEN") + # shellcheck disable=SC2015 + docker login -u rhscl-ci-testing -p "$docker_token" "${INTERNAL_DOCKER_REGISTRY}" && return 0 || : + sleep 5 + done + return 1 +} + +function ct_os_import_image_ocp4() { + local image_name="${1}"; shift + local imagestream=${1:-$image_name:latest} + local namespace + + namespace=${CT_NAMESPACE:-"$(oc project -q)"} + deploy_image_name="${INTERNAL_DOCKER_REGISTRY}/rhscl-ci-testing/${imagestream}" + echo "Uploading image ${image_name} as ${deploy_image_name} , ${imagestream} into external registry." + ct_os_upload_image_external_registry "${image_name}" "${imagestream}" + if [ "${CT_TAG_IMAGE:-false}" == 'true' ]; then + echo "Tag ${deploy_image_name} to ${namespace}/${imagestream}" + oc tag --source=docker "${deploy_image_name}" "${namespace}/${imagestream}" --insecure=true --reference-policy=local + else + echo "Import image into OpenShift 4 environment ${namespace}/${imagestream} from ${deploy_image_name}" + oc import-image "${namespace}/${imagestream}" --from="${deploy_image_name}" --confirm --reference-policy=local + fi +} diff --git a/test/test-lib-ruby.sh b/test/test-lib-ruby.sh new file mode 100644 index 0000000..14bddcb --- /dev/null +++ b/test/test-lib-ruby.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# +# Functions for tests for the Ruby S2I 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 + +function test_ruby_integration() { + local image_name=$1 + ct_os_test_s2i_app "${image_name}" \ + "https://github.com/sclorg/s2i-ruby-container.git" \ + "${VERSION}/test/puma-test-app" \ + ".*" +} + +# Check the imagestream +function test_ruby_imagestream() { + case ${OS} in + rhel7|centos7) ;; + *) echo "Imagestream testing not supported for $OS environment." ; return 0 ;; + esac + + ct_os_test_image_stream_s2i "${THISDIR}/imagestreams/ruby-${OS%[0-9]*}.json" "${IMAGE_NAME}" \ + "https://github.com/sclorg/s2i-ruby-container.git" \ + "${VERSION}/test/puma-test-app" \ + ".*" +} + +# vim: set tabstop=2:shiftwidth=2:expandtab: + diff --git a/test/test-lib.sh b/test/test-lib.sh new file mode 100644 index 0000000..42f22ba --- /dev/null +++ b/test/test-lib.sh @@ -0,0 +1,1072 @@ +# shellcheck shell=bash +# +# Test a container image. +# +# Always use sourced from a specific container testfile +# +# reguires definition of CID_FILE_DIR +# CID_FILE_DIR=$(mktemp --suffix=_test_cidfiles -d) +# reguires definition of TEST_LIST +# TEST_LIST="\ +# ctest_container_creation +# ctest_doc_content" + +# Container CI tests +# abbreviated as "ct" + +# may be redefined in the specific container testfile +EXPECTED_EXIT_CODE=0 + +# ct_cleanup +# -------------------- +# Cleans up containers used during tests. Stops and removes all containers +# referenced by cid_files in CID_FILE_DIR. Dumps logs if a container exited +# unexpectedly. Removes the cid_files and CID_FILE_DIR as well. +# Uses: $CID_FILE_DIR - path to directory containing cid_files +# Uses: $EXPECTED_EXIT_CODE - expected container exit code +function ct_cleanup() { + ct_show_resources + for cid_file in "$CID_FILE_DIR"/* ; do + [ -f "$cid_file" ] || continue + local container + container=$(cat "$cid_file") + + : "Stopping and removing container $container..." + docker stop "$container" + exit_status=$(docker inspect -f '{{.State.ExitCode}}' "$container") + if [ "$exit_status" != "$EXPECTED_EXIT_CODE" ]; then + : "Dumping logs for $container" + docker logs "$container" + fi + docker rm -v "$container" + rm "$cid_file" + done + rmdir "$CID_FILE_DIR" + : "Done." +} + +# ct_enable_cleanup +# -------------------- +# Enables automatic container cleanup after tests. +function ct_enable_cleanup() { + trap ct_cleanup EXIT SIGINT +} + +# ct_pull_image +# ------------- +# Function pull an image before tests execution +# Argument: image_name - string containing the public name of the image to pull +# Argument: exit - in case "true" is defined and pull failed, then script has to exit with 1 and no tests are executed +# Argument: loops - how many times to pull image in case of failure +# Function returns either 0 in case of pull was successful +# Or the test suite exit with 1 in case of pull error +function ct_pull_image() { + local image_name="$1"; shift + local exit=${1:-"false"}; shift + local loops=${1:-10}; shift + local loop=0 + + # Let's try to pull image. + echo "-> Pulling image $image_name ..." + # Sometimes in Fedora case it fails with HTTP 50X + # Check if the image is available locally and try to pull it if it is not + if [[ "$(docker images -q "$image_name" 2>/dev/null)" != "" ]]; then + echo "The image $image_name is already pulled." + return 0 + fi + + # Try pulling the image to see if it is accessible + # WORKAROUND: Since Fedora registry sometimes fails randomly, let's try it more times + while ! docker pull "$image_name"; do + ((loop++)) || : + echo "Pulling image $image_name failed." + if [ "$loop" -gt "$loops" ]; then + echo "Pulling of image $image_name failed $loops times in a row. Giving up." + echo "!!! ERROR with pulling image $image_name !!!!" + # shellcheck disable=SC2268 + if [[ x"$exit" == x"false" ]]; then + return 1 + else + exit 1 + fi + fi + echo "Let's wait $((loop*5)) seconds and try again." + sleep "$((loop*5))" + done +} + + +# ct_check_envs_set env_filter check_envs loop_envs [env_format] +# -------------------- +# Compares values from one list of environment variable definitions against such list, +# checking if the values are present and have a specific format. +# Argument: env_filter - optional string passed to grep used for +# choosing which variables to filter out in env var lists. +# Argument: check_envs - list of env var definitions to check values against +# Argument: loop_envs - list of env var definitions to check values for +# Argument: env_format (optional) - format string for bash substring deletion used +# for checking whether the value is contained in check_envs. +# Defaults to: "*VALUE*", VALUE string gets replaced by actual value from loop_envs +function ct_check_envs_set { + local env_filter check_envs env_format + env_filter=$1; shift + check_envs=$1; shift + loop_envs=$1; shift + env_format=${1:-"*VALUE*"} + while read -r variable; do + [ -z "$variable" ] && continue + var_name=$(echo "$variable" | awk -F= '{ print $1 }') + stripped=$(echo "$variable" | awk -F= '{ print $2 }') + filtered_envs=$(echo "$check_envs" | grep "^$var_name=") + [ -z "$filtered_envs" ] && { echo "$var_name not found during \` docker exec\`"; return 1; } + old_IFS=$IFS + # For each such variable compare its content with the `docker exec` result, use `:` as delimiter + IFS=: + for value in $stripped; do + # If the falue checked does not go through env_filter we do not care about it + echo "$value" | grep -q "$env_filter" || continue + if [ -n "${filtered_envs##${env_format//VALUE/$value}}" ]; then + echo " Value $value is missing from variable $var_name" + echo "$filtered_envs" + IFS=$old_IFS + return 1 + fi + done + IFS=$old_IFS + done <<< "$(echo "$loop_envs" | grep "$env_filter" | grep -v "^PWD=")" +} + +# ct_get_cid [name] +# -------------------- +# Prints container id from cid_file based on the name of the file. +# Argument: name - name of cid_file where the container id will be stored +# Uses: $CID_FILE_DIR - path to directory containing cid_files +function ct_get_cid() { + local name="$1" ; shift || return 1 + cat "$CID_FILE_DIR/$name" +} + +# ct_get_cip [id] +# -------------------- +# Prints container ip address based on the container id. +# Argument: id - container id +function ct_get_cip() { + local id="$1" ; shift + docker inspect --format='{{.NetworkSettings.IPAddress}}' "$(ct_get_cid "$id")" +} + +# ct_wait_for_cid [cid_file] +# -------------------- +# Holds the execution until the cid_file is created. Usually run after container +# creation. +# Argument: cid_file - name of the cid_file that should be created +function ct_wait_for_cid() { + local cid_file=$1 + local max_attempts=10 + local sleep_time=1 + local attempt=1 + local result=1 + while [ $attempt -le $max_attempts ]; do + [ -f "$cid_file" ] && [ -s "$cid_file" ] && return 0 + : "Waiting for container start..." + attempt=$(( attempt + 1 )) + sleep $sleep_time + done + return 1 +} + +# ct_assert_container_creation_fails [container_args] +# -------------------- +# The invocation of docker run should fail based on invalid container_args +# passed to the function. Returns 0 when container fails to start properly. +# Argument: container_args - all arguments are passed directly to dokcer run +# Uses: $CID_FILE_DIR - path to directory containing cid_files +function ct_assert_container_creation_fails() { + local ret=0 + local max_attempts=10 + local attempt=1 + local cid_file=assert + set +e + local old_container_args="${CONTAINER_ARGS-}" + # we really work with CONTAINER_ARGS as with a string + # shellcheck disable=SC2124 + CONTAINER_ARGS="$@" + if ct_create_container "$cid_file" ; then + local cid + cid=$(ct_get_cid "$cid_file") + + while [ "$(docker inspect -f '{{.State.Running}}' "$cid")" == "true" ] ; do + sleep 2 + attempt=$(( attempt + 1 )) + if [ "$attempt" -gt "$max_attempts" ]; then + docker stop "$cid" + ret=1 + break + fi + done + exit_status=$(docker inspect -f '{{.State.ExitCode}}' "$cid") + if [ "$exit_status" == "0" ]; then + ret=1 + fi + docker rm -v "$cid" + rm "$CID_FILE_DIR/$cid_file" + fi + [ -n "$old_container_args" ] && CONTAINER_ARGS="$old_container_args" + set -e + return "$ret" +} + +# ct_create_container [name, command] +# -------------------- +# Creates a container using the IMAGE_NAME and CONTAINER_ARGS variables. Also +# stores the container id to a cid_file located in the CID_FILE_DIR, and waits +# for the creation of the file. +# Argument: name - name of cid_file where the container id will be stored +# Argument: command - optional command to be executed in the container +# Uses: $CID_FILE_DIR - path to directory containing cid_files +# Uses: $CONTAINER_ARGS - optional arguments passed directly to docker run +# Uses: $IMAGE_NAME - name of the image being tested +function ct_create_container() { + local cid_file="$CID_FILE_DIR/$1" ; shift + # create container with a cidfile in a directory for cleanup + # shellcheck disable=SC2086,SC2153 + docker run --cidfile="$cid_file" -d ${CONTAINER_ARGS:-} "$IMAGE_NAME" "$@" + ct_wait_for_cid "$cid_file" || return 1 + : "Created container $(cat "$cid_file")" +} + +# ct_scl_usage_old [name, command, expected] +# -------------------- +# Tests three ways of running the SCL, by looking for an expected string +# in the output of the command +# Argument: name - name of cid_file where the container id will be stored +# Argument: command - executed inside the container +# Argument: expected - string that is expected to be in the command output +# Uses: $CID_FILE_DIR - path to directory containing cid_files +# Uses: $IMAGE_NAME - name of the image being tested +function ct_scl_usage_old() { + local name="$1" + local command="$2" + local expected="$3" + local out="" + : " Testing the image SCL enable" + out=$(docker run --rm "${IMAGE_NAME}" /bin/bash -c "${command}") + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[/bin/bash -c \"${command}\"] Expected '${expected}', got '${out}'" >&2 + return 1 + fi + out=$(docker exec "$(ct_get_cid "$name")" /bin/bash -c "${command}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[exec /bin/bash -c \"${command}\"] Expected '${expected}', got '${out}'" >&2 + return 1 + fi + out=$(docker exec "$(ct_get_cid "$name")" /bin/sh -ic "${command}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[exec /bin/sh -ic \"${command}\"] Expected '${expected}', got '${out}'" >&2 + return 1 + fi +} + +# ct_doc_content_old [strings] +# -------------------- +# Looks for occurence of stirngs in the documentation files and checks +# the format of the files. Files examined: help.1 +# Argument: strings - strings expected to appear in the documentation +# Uses: $IMAGE_NAME - name of the image being tested +function ct_doc_content_old() { + local tmpdir + tmpdir=$(mktemp -d) + local f + : " Testing documentation in the container image" + # Extract the help files from the container + # shellcheck disable=SC2043 + for f in help.1 ; do + docker run --rm "${IMAGE_NAME}" /bin/bash -c "cat /${f}" >"${tmpdir}/$(basename "${f}")" + # Check whether the files contain some important information + for term in "$@" ; do + if ! grep -F -q -e "${term}" "${tmpdir}/$(basename "${f}")" ; then + echo "ERROR: File /${f} does not include '${term}'." >&2 + return 1 + fi + done + # Check whether the files use the correct format + for term in TH PP SH ; do + if ! grep -q "^\.${term}" "${tmpdir}/help.1" ; then + echo "ERROR: /help.1 is probably not in troff or groff format, since '${term}' is missing." >&2 + return 1 + fi + done + done + : " Success!" +} + +# full_ca_file_path +# Return string for full path to CA file +function full_ca_file_path() +{ + echo "/etc/pki/ca-trust/source/anchors/RH-IT-Root-CA.crt" +} +# ct_mount_ca_file +# ------------------ +# Check if /etc/pki/certs/RH-IT-Root-CA.crt file exists +# return mount string for containers or empty string +function ct_mount_ca_file() +{ + # mount CA file only if NPM_REGISTRY variable is present. + local mount_parameter="" + if [ -n "$NPM_REGISTRY" ] && [ -f "$(full_ca_file_path)" ]; then + mount_parameter="-v $(full_ca_file_path):$(full_ca_file_path):Z" + fi + echo "$mount_parameter" +} + +# ct_build_s2i_npm_variables URL_TO_NPM_JS_SERVER +# ------------------------------------------ +# Function returns -e NPM_MIRROR and -v MOUNT_POINT_FOR_CAFILE +# or empty string +function ct_build_s2i_npm_variables() +{ + npm_variables="" + if [ -n "$NPM_REGISTRY" ] && [ -f "$(full_ca_file_path)" ]; then + npm_variables="-e NPM_MIRROR=$NPM_REGISTRY $(ct_mount_ca_file)" + fi + echo "$npm_variables" +} + +# ct_npm_works +# -------------------- +# Checks existance of the npm tool and runs it. +function ct_npm_works() { + local tmpdir + tmpdir=$(mktemp -d) + : " Testing npm in the container image" + local cid_file="${tmpdir}/cid" + if ! docker run --rm "${IMAGE_NAME}" /bin/bash -c "npm --version" >"${tmpdir}/version" ; then + echo "ERROR: 'npm --version' does not work inside the image ${IMAGE_NAME}." >&2 + return 1 + fi + + # shellcheck disable=SC2046 + docker run -d $(ct_mount_ca_file) --rm --cidfile="$cid_file" "${IMAGE_NAME}-testapp" + + # Wait for the container to write it's CID file + ct_wait_for_cid "$cid_file" || return 1 + + if ! docker exec "$(cat "$cid_file")" /bin/bash -c "npm --verbose install jquery && test -f node_modules/jquery/src/jquery.js" >"${tmpdir}/jquery" 2>&1 ; then + echo "ERROR: npm could not install jquery inside the image ${IMAGE_NAME}." >&2 + cat "${tmpdir}/jquery" + return 1 + fi + + if [ -n "$NPM_REGISTRY" ] && [ -f "$(full_ca_file_path)" ]; then + if ! grep -qo "$NPM_REGISTRY" "${tmpdir}/jquery"; then + echo "ERROR: Internal repository is NOT set. Even it is requested." + return 1 + fi + fi + + if [ -f "$cid_file" ]; then + docker stop "$(cat "$cid_file")" + rm "$cid_file" + fi + : " Success!" +} + +# ct_binary_found_from_df binary [path] +# -------------------- +# Checks if a binary can be found in PATH during Dockerfile build +# Argument: binary - name of the binary to test accessibility for +# Argument: path - optional path in which the binary should reside in +# /opt/rh by default +function ct_binary_found_from_df() { + local tmpdir + local binary=$1; shift + local binary_path=${1:-"^/opt/rh"} + tmpdir=$(mktemp -d) + : " Testing $binary in build from Dockerfile" + + # Create Dockerfile that looks for the binary + cat <"$tmpdir/Dockerfile" +FROM $IMAGE_NAME +RUN command -v $binary | grep "$binary_path" +EOF + # Build an image, looking for expected path in the output + if ! docker build -f "$tmpdir/Dockerfile" --no-cache "$tmpdir"; then + echo " ERROR: Failed to find $binary in Dockerfile!" >&2 + return 1 + fi + : " Success!" +} + +# ct_check_exec_env_vars [env_filter] +# -------------------- +# Checks if all relevant environment variables from `docker run` +# can be found in `docker exec` as well. +# Argument: env_filter - optional string passed to grep used for +# choosing which variables to check in the test case. +# Defaults to X_SCLS and variables containing /opt/app-root, /opt/rh +# Uses: $CID_FILE_DIR - path to directory containing cid_files +# Uses: $IMAGE_NAME - name of the image being tested +function ct_check_exec_env_vars() { + local tmpdir exec_envs cid old_IFS env_filter + local var_name stripped filtered_envs run_envs + env_filter=${1:-"^X_SCLS=\|/opt/rh\|/opt/app-root"} + tmpdir=$(mktemp -d) + CID_FILE_DIR=${CID_FILE_DIR:-$(mktemp -d)} + # Get environment variables from `docker run` + run_envs=$(docker run --rm "$IMAGE_NAME" /bin/bash -c "env") + # Get environment variables from `docker exec` + ct_create_container "test_exec_envs" bash -c "sleep 1000" >/dev/null + cid=$(ct_get_cid "test_exec_envs") + exec_envs=$(docker exec "$cid" env) + # Filter out variables we are not interested in + # Always check X_SCLS, ignore PWD + # Check variables from `docker run` that have alternative paths inside (/opt/rh, /opt/app-root) + ct_check_envs_set "$env_filter" "$exec_envs" "$run_envs" "*VALUE*" || return 1 + echo " All values present in \`docker exec\`" + return 0 +} + +# ct_check_scl_enable_vars [env_filter] +# -------------------- +# Checks if all relevant environment variables from `docker run` +# are set twice after a second call of `scl enable $SCLS`. +# Argument: env_filter - optional string passed to grep used for +# choosing which variables to check in the test case. +# Defaults to paths containing enabled SCLS in the image +# Uses: $IMAGE_NAME - name of the image being tested +function ct_check_scl_enable_vars() { + local tmpdir exec_envs cid old_IFS env_filter enabled_scls + local var_name stripped filtered_envs loop_envs + env_filter=$1 + tmpdir=$(mktemp -d) + enabled_scls=$(docker run --rm "$IMAGE_NAME" /bin/bash -c "echo \$X_SCLS") + if [ -z "$env_filter" ]; then + for scl in $enabled_scls; do + [ -z "$env_filter" ] && env_filter="/$scl" && continue + # env_filter not empty, append to the existing list + env_filter="$env_filter|/$scl" + done + fi + # Get environment variables from `docker run` + loop_envs=$(docker run --rm "$IMAGE_NAME" /bin/bash -c "env") + run_envs=$(docker run --rm "$IMAGE_NAME" /bin/bash -c "X_SCLS= scl enable $enabled_scls env") + # Check if the values are set twice in the second set of envs + ct_check_envs_set "$env_filter" "$run_envs" "$loop_envs" "*VALUE*VALUE*" || return 1 + echo " All scl_enable values present" + return 0 +} + +# ct_path_append PATH_VARNAME DIRECTORY +# ------------------------------------- +# Append DIRECTORY to VARIABLE of name PATH_VARNAME, the VARIABLE must consist +# of colon-separated list of directories. +ct_path_append () +{ + if eval "test -n \"\${$1-}\""; then + eval "$1=\$2:\$$1" + else + eval "$1=\$2" + fi +} + + +# ct_path_foreach PATH ACTION [ARGS ...] +# -------------------------------------- +# For each DIR in PATH execute ACTION (path is colon separated list of +# directories). The particular calls to ACTION will look like +# '$ ACTION directory [ARGS ...]' +ct_path_foreach () +{ + local dir dirlist action save_IFS + save_IFS=$IFS + IFS=: + dirlist=$1 + action=$2 + shift 2 + for dir in $dirlist; do "$action" "$dir" "$@" ; done + IFS=$save_IFS +} + + +# ct_run_test_list +# -------------------- +# Execute the tests specified by TEST_LIST +# Uses: $TEST_LIST - list of test names +function ct_run_test_list() { + for test_case in $TEST_LIST; do + : "Running test $test_case" + # shellcheck source=/dev/null + [ -f "test/$test_case" ] && source "test/$test_case" + # shellcheck source=/dev/null + [ -f "../test/$test_case" ] && source "../test/$test_case" + $test_case + done; +} + +# ct_gen_self_signed_cert_pem +# --------------------------- +# Generates a self-signed PEM certificate pair into specified directory. +# Argument: output_dir - output directory path +# Argument: base_name - base name of the certificate files +# Resulted files will be those: +# /-cert-selfsigned.pem -- public PEM cert +# /-key.pem -- PEM private key +ct_gen_self_signed_cert_pem() { + local output_dir=$1 ; shift + local base_name=$1 ; shift + mkdir -p "${output_dir}" + openssl req -newkey rsa:2048 -nodes -keyout "${output_dir}"/"${base_name}"-key.pem -subj '/C=GB/ST=Berkshire/L=Newbury/O=My Server Company' > "${base_name}"-req.pem + openssl req -new -x509 -nodes -key "${output_dir}"/"${base_name}"-key.pem -batch > "${output_dir}"/"${base_name}"-cert-selfsigned.pem +} + +# ct_obtain_input FILE|DIR|URL +# -------------------- +# Either copies a file or a directory to a tmp location for local copies, or +# downloads the file from remote location. +# Resulted file path is printed, so it can be later used by calling function. +# Arguments: input - local file, directory or remote URL +function ct_obtain_input() { + local input=$1 + local extension="${input##*.}" + + # Try to use same extension for the temporary file if possible + [[ "${extension}" =~ ^[a-z0-9]*$ ]] && extension=".${extension}" || extension="" + + local output + output=$(mktemp "/var/tmp/test-input-XXXXXX$extension") + if [ -f "${input}" ] ; then + cp -f "${input}" "${output}" + elif [ -d "${input}" ] ; then + rm -f "${output}" + cp -r -LH "${input}" "${output}" + elif echo "${input}" | grep -qe '^http\(s\)\?://' ; then + curl "${input}" > "${output}" + else + echo "ERROR: file type not known: ${input}" >&2 + return 1 + fi + echo "${output}" +} + +# ct_test_response +# ---------------- +# Perform GET request to the application container, checks output with +# a reg-exp and HTTP response code. +# Argument: url - request URL path +# Argument: expected_code - expected HTTP response code +# Argument: body_regexp - PCRE regular expression that must match the response body +# Argument: max_attempts - Optional number of attempts (default: 20), three seconds sleep between +# Argument: ignore_error_attempts - Optional number of attempts when we ignore error output (default: 10) +ct_test_response() { + local url="$1" + local expected_code="$2" + local body_regexp="$3" + local max_attempts=${4:-20} + local ignore_error_attempts=${5:-10} + + : " Testing the HTTP(S) response for <${url}>" + local sleep_time=3 + local attempt=1 + local result=1 + local status + local response_code + local response_file + response_file=$(mktemp /tmp/ct_test_response_XXXXXX) + while [ "${attempt}" -le "${max_attempts}" ]; do + curl --connect-timeout 10 -s -w '%{http_code}' "${url}" >"${response_file}" && status=0 || status=1 + if [ "${status}" -eq 0 ]; then + response_code=$(tail -c 3 "${response_file}") + if [ "${response_code}" -eq "${expected_code}" ]; then + result=0 + fi + grep -qP -e "${body_regexp}" "${response_file}" || result=1; + # Some services return 40x code until they are ready, so let's give them + # some chance and not end with failure right away + # Do not wait if we already have expected outcome though + if [ "${result}" -eq 0 ] || [ "${attempt}" -gt "${ignore_error_attempts}" ] || [ "${attempt}" -eq "${max_attempts}" ] ; then + break + fi + fi + attempt=$(( attempt + 1 )) + sleep "${sleep_time}" + done + rm -f "${response_file}" + return "${result}" +} + +# ct_registry_from_os OS +# ---------------- +# Transform operating system string [os] into registry url +# Argument: OS - string containing the os version +ct_registry_from_os() { + local registry="" + case $1 in + rhel*) + registry=registry.redhat.io + ;; + *) + registry=quay.io + ;; + esac + echo "$registry" +} + + # ct_get_public_image_name OS BASE_IMAGE_NAME VERSION +# ---------------- +# Transform the arguments into public image name +# Argument: OS - string containing the os version +# Argument: BASE_IMAGE_NAME - string containing the base name of the image as defined in the Makefile +# Argument: VERSION - string containing the version of the image as defined in the Makefile +ct_get_public_image_name() { + local os=$1; shift + local base_image_name=$1; shift + local version=$1; shift + + local public_image_name + local registry + + registry=$(ct_registry_from_os "$os") + if [ "$os" == "rhel7" ]; then + public_image_name=$registry/rhscl/$base_image_name-${version//./}-rhel7 + elif [ "$os" == "rhel8" ]; then + public_image_name=$registry/rhel8/$base_image_name-${version//./} + elif [ "$os" == "centos7" ]; then + public_image_name=$registry/centos7/$base_image_name-${version//./}-centos7 + elif [ "$os" == "centos8" ]; then + public_image_name=$registry/centos8/$base_image_name-${version//./}-centos8 + fi + + echo "$public_image_name" +} + +# ct_assert_cmd_success CMD +# ---------------- +# Evaluates [cmd] and fails if it does not succeed. +# Argument: CMD - Command to be run +function ct_assert_cmd_success() { + echo "Checking '$*' for success ..." + if ! eval "$@" &>/dev/null; then + echo " FAIL" + return 1 + fi + echo " PASS" + return 0 +} + +# ct_assert_cmd_failure CMD +# ---------------- +# Evaluates [cmd] and fails if it succeeds. +# Argument: CMD - Command to be run +function ct_assert_cmd_failure() { + echo "Checking '$*' for failure ..." + if eval "$@" &>/dev/null; then + echo " FAIL" + return 1 + fi + echo " PASS" + return 0 +} + + +# ct_random_string [LENGTH=10] +# ---------------------------- +# Generate pseudorandom alphanumeric string of LENGTH bytes, the +# default length is 10. The string is printed on stdout. +ct_random_string() +( + export LC_ALL=C + dd if=/dev/urandom count=1 bs=10k 2>/dev/null \ + | tr -dc 'a-z0-9' \ + | fold -w "${1-10}" \ + | head -n 1 +) + +# ct_s2i_usage IMG_NAME [S2I_ARGS] +# ---------------------------- +# Create a container and run the usage script inside +# Argument: IMG_NAME - name of the image to be used for the container run +# Argument: S2I_ARGS - Additional list of source-to-image arguments, currently unused. +ct_s2i_usage() +{ + local img_name=$1; shift + local s2i_args="$*"; + local usage_command="/usr/libexec/s2i/usage" + docker run --rm "$img_name" bash -c "$usage_command" +} + +# ct_s2i_build_as_df APP_PATH SRC_IMAGE DST_IMAGE [S2I_ARGS] +# ---------------------------- +# Create a new s2i app image from local sources in a similar way as source-to-image would have used. +# Argument: APP_PATH - local path to the app sources to be used in the test +# Argument: SRC_IMAGE - image to be used as a base for the s2i build +# Argument: DST_IMAGE - image name to be used during the tagging of the s2i build result +# Argument: S2I_ARGS - Additional list of source-to-image arguments. +# Only used to check for pull-policy=never and environment variable definitions. +ct_s2i_build_as_df() +{ + local app_path=$1; shift + local src_image=$1; shift + local dst_image=$1; shift + local s2i_args="$*"; + local local_app=upload/src/ + local local_scripts=upload/scripts/ + local user_id= + local df_name= + local tmpdir= + local incremental=false + local mount_options=() + + # Run the entire thing inside a subshell so that we do not leak shell options outside of the function + ( + # Error out if any part of the build fails + set -e + + # Use /tmp to not pollute cwd + tmpdir=$(mktemp -d) + df_name=$(mktemp -p "$tmpdir" Dockerfile.XXXX) + cd "$tmpdir" + # Check if the image is available locally and try to pull it if it is not + docker images "$src_image" &>/dev/null || echo "$s2i_args" | grep -q "pull-policy=never" || docker pull "$src_image" + user=$(docker inspect -f "{{.Config.User}}" "$src_image") + # Default to root if no user is set by the image + user=${user:-0} + # run the user through the image in case it is non-numeric or does not exist + if ! user_id=$(ct_get_uid_from_image "$user" "$src_image"); then + echo "Terminating s2i build." + return 1 + fi + + echo "$s2i_args" | grep -q "\-\-incremental" && incremental=true + if $incremental; then + inc_tmp=$(mktemp -d --tmpdir incremental.XXXX) + setfacl -m "u:$user_id:rwx" "$inc_tmp" + # Check if the image exists, build should fail (for testing use case) if it does not + docker images "$dst_image" &>/dev/null || (echo "Image $dst_image not found."; false) + # Run the original image with a mounted in volume and get the artifacts out of it + cmd="if [ -s /usr/libexec/s2i/save-artifacts ]; then /usr/libexec/s2i/save-artifacts > \"$inc_tmp/artifacts.tar\"; else touch \"$inc_tmp/artifacts.tar\"; fi" + docker run --rm -v "$inc_tmp:$inc_tmp:Z" "$dst_image" bash -c "$cmd" + # Move the created content into the $tmpdir for the build to pick it up + mv "$inc_tmp/artifacts.tar" "$tmpdir/" + fi + # Strip file:// from APP_PATH and copy its contents into current context + mkdir -p "$local_app" + cp -r "${app_path/file:\/\//}/." "$local_app" + [ -d "$local_app/.s2i/bin/" ] && mv "$local_app/.s2i/bin" "$local_scripts" + # Create a Dockerfile named df_name and fill it with proper content + #FIXME: Some commands could be combined into a single layer but not sure if worth the trouble for testing purposes + cat <"$df_name" +FROM $src_image +LABEL "io.openshift.s2i.build.image"="$src_image" \\ + "io.openshift.s2i.build.source-location"="$app_path" +USER root +COPY $local_app /tmp/src +EOF + [ -d "$local_scripts" ] && echo "COPY $local_scripts /tmp/scripts" >> "$df_name" && + echo "RUN chown -R $user_id:0 /tmp/scripts" >>"$df_name" + echo "RUN chown -R $user_id:0 /tmp/src" >>"$df_name" + # Check for custom environment variables inside .s2i/ folder + if [ -e "$local_app/.s2i/environment" ]; then + # Remove any comments and add the contents as ENV commands to the Dockerfile + sed '/^\s*#.*$/d' "$local_app/.s2i/environment" | while read -r line; do + echo "ENV $line" >>"$df_name" + done + fi + # Filter out env var definitions from $s2i_args and create Dockerfile ENV commands out of them + echo "$s2i_args" | grep -o -e '\(-e\|--env\)[[:space:]=]\S*=\S*' | sed -e 's/-e /ENV /' -e 's/--env[ =]/ENV /' >>"$df_name" + # Check if CA autority is present on host and add it into Dockerfile + [ -f "$(full_ca_file_path)" ] && echo "RUN cd /etc/pki/ca-trust/source/anchors && update-ca-trust extract" >>"$df_name" + + # Add in artifacts if doing an incremental build + if $incremental; then + { echo "RUN mkdir /tmp/artifacts" + echo "ADD artifacts.tar /tmp/artifacts" + echo "RUN chown -R $user_id:0 /tmp/artifacts" ; } >>"$df_name" + fi + + echo "USER $user_id" >>"$df_name" + # If exists, run the custom assemble script, else default to /usr/libexec/s2i/assemble + if [ -x "$local_scripts/assemble" ]; then + echo "RUN /tmp/scripts/assemble" >>"$df_name" + else + echo "RUN /usr/libexec/s2i/assemble" >>"$df_name" + fi + # If exists, set the custom run script as CMD, else default to /usr/libexec/s2i/run + if [ -x "$local_scripts/run" ]; then + echo "CMD /tmp/scripts/run" >>"$df_name" + else + echo "CMD /usr/libexec/s2i/run" >>"$df_name" + fi + + # Check if -v parameter is present in s2i_args and add it into docker build command + read -ra mount_options <<< "$(echo "$s2i_args" | grep -o -e '\(-v\)[[:space:]]\.*\S*' || true)" + + # Run the build and tag the result + docker build ${mount_options[@]+"${mount_options[@]}"} -f "$df_name" --no-cache=true -t "$dst_image" . + ) +} + +# ct_s2i_multistage_build APP_PATH SRC_IMAGE DST_IMAGE SEC_IMAGE [S2I_ARGS] +# ---------------------------- +# Create a new s2i app image from local sources in a similar way as source-to-image would have used. +# Argument: APP_PATH - local path to the app sources to be used in the test +# Argument: SRC_IMAGE - image to be used as a base for the s2i build process +# Argument: SEC_IMAGE - image to be used as the base for the result of the build process +# Argument: DST_IMAGE - image name to be used during the tagging of the s2i build result +# Argument: S2I_ARGS - Additional list of source-to-image arguments. +# Only used to check for environment variable definitions. +ct_s2i_multistage_build() { + + local app_path=$1; shift + local src_image=$1; shift + local sec_image=$1; shift + local dst_image=$1; shift + local s2i_args=$*; + local local_app="app-src" + local user_id= + local mount_options=() + + + # Run the entire thing inside a subshell so that we do not leak shell options outside of the function + ( + # Error out if any part of the build fails + set -e + + user=$(docker inspect -f "{{.Config.User}}" "$src_image") + # Default to root if no user is set by the image + user=${user:-0} + # run the user through the image in case it is non-numeric or does not exist + if ! user_id=$(ct_get_uid_from_image "$user" "$src_image"); then + echo "Terminating s2i build." + return 1 + fi + + # Use /tmp to not pollute cwd + tmpdir=$(mktemp -d) + df_name=$(mktemp -p "$tmpdir" Dockerfile.XXXX) + cd "$tmpdir" + + # If the path exists on the local host, copy it into the directory for the build + # Otherwise handle it as a link to a git repository + if [ -e "${app_path/file:\/\//}/." ] ; then + mkdir -p "$local_app" + # Strip file:// from APP_PATH and copy its contents into current context + cp -r "${app_path/file:\/\//}/." "$local_app" + + else + ct_clone_git_repository "$app_path" "$local_app" + fi + + cat <"$df_name" +# First stage builds the application +FROM $src_image 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 chown -R 1001:0 /tmp/src +$(echo "$s2i_args" | grep -o -e '\(-e\|--env\)[[:space:]=]\S*=\S*' | sed -e 's/-e /ENV /' -e 's/--env[ =]/ENV /') +# Check if CA autority is present on host and add it into Dockerfile +$([ -f "$(full_ca_file_path)" ] && echo "RUN cd /etc/pki/ca-trust/source/anchors && update-ca-trust extract") +USER $user_id +# Install the dependencies +RUN /usr/libexec/s2i/assemble +# Second stage copies the application to the minimal image +FROM $sec_image +# Copy the application source and build artifacts from the builder image to this one +COPY --from=builder \$HOME \$HOME +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +EOF + + # Check if -v parameter is present in s2i_args and add it into docker build command + read -ra mount_options <<< "$(echo "$s2i_args" | grep -o -e '\(-v\)[[:space:]]\.*\S*' || true)" + + docker build ${mount_options[@]+"${mount_options[@]}"} -f "$df_name" --no-cache=true -t "$dst_image" . + ) +} + +# ct_check_image_availability PUBLIC_IMAGE_NAME +# ---------------------------- +# Pull an image from the public repositories to see if the image is already available. +# Argument: PUBLIC_IMAGE_NAME - string containing the public name of the image to pull +ct_check_image_availability() { + local public_image_name=$1; + + # Try pulling the image to see if it is accessible + if ! ct_pull_image "$public_image_name" &>/dev/null; then + echo "$public_image_name could not be downloaded via 'docker'" + return 1 + fi +} + +# ct_check_latest_imagestreams +# ----------------------------- +# Check if the latest version present in Makefile in the variable VERSIONS +# is present in all imagestreams. +# Also the latest tag in the imagestreams has to contain the latest version +ct_check_latest_imagestreams() { + local latest_version= + local test_lib_dir= + + # We only maintain imagestreams for RHEL and CentOS (Community) + if [[ "$OS" =~ ^fedora.* ]] ; then + echo "Imagestreams for Fedora are not maintained, skipping ct_check_latest_imagestreams" + return 0 + fi + + # Check only lines which starts with VERSIONS + latest_version=$(grep '^VERSIONS' Makefile | rev | cut -d ' ' -f 1 | rev ) + # Fall back to previous version if the latest is excluded for this OS + [ -f "$latest_version/.exclude-$OS" ] && latest_version=$(grep '^VERSIONS' Makefile | rev | cut -d ' ' -f 2 | rev ) + # Only test the imagestream once, when the version matches + # ignore the SC warning, $VERSION is always available + # shellcheck disable=SC2153 + if [ "$latest_version" == "$VERSION" ]; then + test_lib_dir=$(dirname "$(readlink -f "$0")") + python3 "${test_lib_dir}/check_imagestreams.py" "$latest_version" + else + echo "Image version $VERSION is not latest, skipping ct_check_latest_imagestreams" + fi +} + +# ct_show_resources +# ---------------- +# Prints the available resources +ct_show_resources() +{ + echo "Resources info:" + echo "Memory:" + free -h + echo "Storage:" + df -h || : + echo "CPU" + lscpu +} + +# ct_clone_git_repository +# ----------------------------- +# Argument: app_url - git URI pointing to a repository, supports "@" to indicate a different branch +# Argument: app_dir (optional) - name of the directory to clone the repository into +ct_clone_git_repository() +{ + local app_url=$1; shift + local app_dir=$1 + + # If app_url contains @, the string after @ is considered + # as a name of a branch to clone instead of the main/master branch + IFS='@' read -ra git_url_parts <<< "${app_url}" + + if [ -n "${git_url_parts[1]}" ]; then + git_clone_cmd="git clone --branch ${git_url_parts[1]} ${git_url_parts[0]} ${app_dir}" + else + git_clone_cmd="git clone ${app_url} ${app_dir}" + fi + + if ! $git_clone_cmd ; then + echo "ERROR: Git repository ${app_url} cannot be cloned into ${app_dir}." + return 1 + fi +} + +# ct_get_uid_from_image +# ----------------------------- +# Argument: user - user to get uid for inside the image +# Argument: src_image - image to use for user information +ct_get_uid_from_image() +{ + local user=$1; shift + local src_image=$1 + local user_id= + + # NOTE: The '-eq' test is used to check if $user is numeric as it will fail if $user is not an integer + if ! [ "$user" -eq "$user" ] 2>/dev/null && ! user_id=$(docker run --rm "$src_image" bash -c "id -u $user 2>/dev/null"); then + echo "ERROR: id of user $user not found inside image $src_image." + return 1 + else + echo "${user_id:-$user}" + fi +} + +# ct_test_app_dockerfile +# ----------------------------- +# Argument: dockerfile - path to a Dockerfile that will be used for building an image +# (must work with an application directory called 'app-src') +# Argument: app_url - git or local URI with a testing application, supports "@" to indicate a different branch +# Argument: body_regexp - PCRE regular expression that must match the response body +# Argument: app_dir - name of the application directory that is used in the Dockerfile +# Argument: port - Optional port number (default: 8080) +ct_test_app_dockerfile() { + local dockerfile=$1 + local app_url=$2 + local expected_text=$3 + local app_dir=$4 # this is a directory that must match with the name in the Dockerfile + local port=${5:-8080} + local app_image_name=myapp + local ret + local cname=app_dockerfile + + if [ -z "$app_dir" ] ; then + echo "ERROR: Option app_dir not set. Terminating the Dockerfile build." + return 1 + fi + + if ! [ -r "${dockerfile}" ] || ! [ -s "${dockerfile}" ] ; then + echo "ERROR: Dockerfile ${dockerfile} does not exist or is empty." + echo "Terminating the Dockerfile build." + return 1 + fi + + CID_FILE_DIR=${CID_FILE_DIR:-$(mktemp -d)} + local dockerfile_abs + dockerfile_abs=$(readlink -f "${dockerfile}") + tmpdir=$(mktemp -d) + pushd "$tmpdir" >/dev/null + cp "${dockerfile_abs}" Dockerfile + + # Rewrite the source image to what we test + sed -i -e "s|^FROM.*$|FROM $IMAGE_NAME|" Dockerfile + # a bit more verbose, but should help debugging failures + echo "Using this Dockerfile:" + cat Dockerfile + + if [ -d "$app_url" ] ; then + echo "Copying local folder: $app_url -> $app_dir." + cp -Lr "$app_url" "$app_dir" + else + if ! ct_clone_git_repository "$app_url" "$app_dir" ; then + echo "Terminating the Dockerfile build." + return 1 + fi + fi + + echo "Building '${app_image_name}' image using docker build" + if ! docker build --no-cache=true -t "${app_image_name}" . ; then + echo "ERROR: The image cannot be built from ${dockerfile} and application ${app_url}." + echo "Terminating the Dockerfile build." + return 1 + fi + + if ! docker run -d --cidfile="${CID_FILE_DIR}/app_dockerfile" --rm "${app_image_name}" ; then + echo "ERROR: The image ${app_image_name} cannot be run for ${dockerfile} and application ${app_url}." + echo "Terminating the Dockerfile build." + return 1 + fi + echo "Waiting for ${app_image_name} to start" + ct_wait_for_cid "${CID_FILE_DIR}/app_dockerfile" + + ip="$(ct_get_cip "${cname}")" + ct_test_response "http://$ip:${port}" 200 "${expected_text}" + ret=$? + + # cleanup + docker kill "$(ct_get_cid "${cname}")" + sleep 2 + docker rmi "${app_image_name}" + popd >/dev/null + rm -rf "${tmpdir}" + rm -f "${CID_FILE_DIR}/${cname}" + return $ret +} + +# vim: set tabstop=2:shiftwidth=2:expandtab: diff --git a/test/test_s2i.py b/test/test_s2i.py new file mode 100644 index 0000000..b632fe1 --- /dev/null +++ b/test/test_s2i.py @@ -0,0 +1,84 @@ +#!/usr/bin/python3 +import logging +import os +import time + +from conu import DockerBackend, S2IDockerImage, Probe, DockerRunBuilder + +import pytest + + +image_name = os.environ.get("IMAGE_NAME", "ruby").split(":")[0] +image_tag = os.environ.get("IMAGE_NAME", "ruby").split(":")[1] +test_dir = os.path.abspath(os.path.dirname(__file__)) +puma_app_path = os.path.join(test_dir, "puma-test-app") +rack_app_path = os.path.join(test_dir, "rack-test-app") +app_paths = [ + puma_app_path, + rack_app_path +] + + +backend = DockerBackend(logging_level=logging.DEBUG) + + +@pytest.fixture(scope="module", params=app_paths) +def app(request): + i = S2IDockerImage(image_name, tag=image_tag) + app_name = os.path.basename(request.param) + app = i.extend(request.param, app_name) + yield app + pass + app.rmi() + + +class TestSuite: + def test_s2i_apps(self, app): + c = app.run_via_binary() + try: + c.wait_for_port(8080) + assert c.is_port_open(8080) + response = c.http_request("/", port="8080") + assert response.ok + output = c.execute(["bash", "-c", "ruby --version"])[0] + expected = "ruby %s." % os.environ["VERSION"] + assert expected in output.decode("utf-8") + finally: + c.stop() + c.wait() + # debugging + print(list(c.logs())) + c.delete() + + def test_invoking_container(self): + image = backend.ImageClass(image_name, tag=image_tag) + c = image.run_via_binary(DockerRunBuilder(command=["bash", "-c", "ruby --version"])) + try: + c.wait() + logs = list(c.logs())[0].decode("utf-8") + finally: + c.stop() + c.wait() + c.delete() + assert "ruby " in logs + if os.environ.get("VERSION", None): + assert os.environ["VERSION"] in logs + + def test_usage(self): + i = S2IDockerImage(image_name, tag=image_tag) + c = i.run_via_binary() + + def logs_received(): + return len(list(c.logs())) > 0 + + try: + c.wait() + # even after waiting there is still a race in journal logging driver + Probe(timeout=10, pause=0.05, count=20, fnc=logs_received).run() + logs = [x.decode("utf-8") for x in c.logs()] + logs = "\n".join(logs).strip() + usage = i.usage() + # FIXME: workaround: `docker logs` can't handle logs like these: '\n\n\n' + assert logs.replace("\n", "") == usage.replace("\n", "") + finally: + c.delete()