diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8f47146 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +FROM registry.fedoraproject.org/f25/s2i-base:latest + +# This image provides a Ruby 2.4 environment you can use to run your Ruby +# applications. + +LABEL MAINTAINER SoftwareCollections.org + +EXPOSE 8080 + +ENV RUBY_VERSION 2.4 + +LABEL summary="Platform for building and running Ruby 2.4 applications" \ + io.k8s.description="Platform for building and running Ruby 2.4 applications" \ + io.k8s.display-name="Ruby 2.4" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,ruby,ruby24,rh-ruby24" + +ENV NAME=ruby VERSION=0 RELEASE=12 ARCH=x86_64 + +LABEL com.redhat.component="$NAME" \ + name="$FGC/$NAME" \ + version="$VERSION" \ + eelease="$RELEASE.$DISTTAG" \ + architecture="$ARCH" \ + usage="s2i build file:///your/app 25/ruby your-app" \ + help="help.1" + +RUN INSTALL_PKGS="ruby ruby-devel rubygem-rake rubygem-bundler nodejs" && \ + dnf install -y --setopt=tsflags=nodocs $INSTALL_PKGS && rpm -V $INSTALL_PKGS && \ + dnf clean all -y + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH +COPY ./s2i/bin/ $STI_SCRIPTS_PATH + +# Each language image can have 'contrib' a directory with extra files needed to +# run and build the applications. +COPY ./contrib/ /opt/app-root + +RUN chown -R 1001:0 /opt/app-root && chmod -R ug+rwx /opt/app-root + +USER 1001 + +# Set the default CMD to print the usage of the language image +CMD $STI_SCRIPTS_PATH/usage diff --git a/README.md b/README.md new file mode 100644 index 0000000..328d2a2 --- /dev/null +++ b/README.md @@ -0,0 +1,175 @@ +Ruby 2.4 platform for building and running applications +======================================================= + +This repository contains the source for building various versions of +the Ruby application as a reproducible Docker image using +[source-to-image](https://github.com/openshift/source-to-image). +Users can choose between RHEL and CentOS based builder images. +The resulting image can be run using [Docker](http://docker.io). + + +Usage +--------------------- +To build a simple [ruby-sample-app](https://github.com/openshift/sti-ruby/tree/master/2.3/test/puma-test-app) application +using standalone [S2I](https://github.com/openshift/source-to-image) and then run the +resulting image with [Docker](http://docker.io) execute: + +* **For RHEL based image** + ``` + $ s2i build https://github.com/openshift/sti-ruby.git --context-dir=2.3/test/puma-test-app/ rhscl/ruby-23-rhel7 ruby-sample-app + $ docker run -p 8080:8080 ruby-sample-app + ``` + +* **For CentOS based image** + ``` + $ s2i build https://github.com/openshift/sti-ruby.git --context-dir=2.3/test/puma-test-app/ centos/ruby-23-centos7 ruby-sample-app + $ docker run -p 8080:8080 ruby-sample-app + ``` + +**Accessing the application:** +``` +$ curl 127.0.0.1:8080 +``` + + +Repository organization +------------------------ +* **``** + + * **Dockerfile** + + CentOS based Dockerfile. + + * **Dockerfile.rhel7** + + RHEL based Dockerfile. In order to perform build or test actions on this + Dockerfile you need to run the action on a properly subscribed RHEL machine. + + * **`s2i/bin/`** + + This folder contains scripts that are run by [S2I](https://github.com/openshift/source-to-image): + + * **assemble** + + Used to install the sources into the location where the application + will be run and prepare the application for deployment (eg. installing + modules using bundler, etc.) + + * **run** + + This script is responsible for running the application by using the + application web server. + + * **usage*** + + This script prints the usage of this image. + + * **`contrib/`** + + This folder contains a file with commonly used modules. + + * **`test/`** + + This folder contains a [S2I](https://github.com/openshift/source-to-image) + test framework with a simple Rack server. + + * **`puma-test-app/`** + + Simple Puma web server used for testing purposes by the [S2I](https://github.com/openshift/source-to-image) test framework. + + * **`rack-test-app/`** + + Simple Rack web server used for testing purposes by the [S2I](https://github.com/openshift/source-to-image) test framework. + + * **run** + + Script that runs the [S2I](https://github.com/openshift/source-to-image) test framework. + + +Environment variables +--------------------- + +To set these environment variables, you can place them as a key value pair into a `.sti/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). + +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 [Docker](http://docker.io) `-e` run flag: + ``` + $ docker 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 [Docker](http://docker.io) -e run flag: + ``` + $ docker run -e RACK_ENV=development -p 8080:8080 sinatra-app + ``` + +To change your source code in running container, use Docker's [exec](http://docker.io) command: +``` +docker exec -it /bin/bash +``` + +After you [Docker exec](http://docker.io) 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 [Docker](http://docker.io) run command: +``` +$ docker 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: +``` +$ docker 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`. diff --git a/contrib/.gemrc b/contrib/.gemrc new file mode 100644 index 0000000..2422bea --- /dev/null +++ b/contrib/.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/contrib/etc/puma.cfg b/contrib/etc/puma.cfg new file mode 100644 index 0000000..090923b --- /dev/null +++ b/contrib/etc/puma.cfg @@ -0,0 +1,40 @@ +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_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 cores. +def get_workers() + return ENV['PUMA_WORKERS'].to_i if ENV.has_key? 'PUMA_WORKERS' + + base_memory = 50 * 1024 * 1024 + per_worker_base_memory = 15 * 1024 * 1024 + per_thread_memory = 128 * 1024 + + cores = ENV.fetch('NUMBER_OF_CORES', '1').to_i + per_worker_memory = per_worker_base_memory + per_thread_memory*get_max_threads() + max_workers = (get_max_memory() - base_memory) / per_worker_memory + + [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/dead.package b/dead.package deleted file mode 100644 index 7290702..0000000 --- a/dead.package +++ /dev/null @@ -1 +0,0 @@ -container sources not used for building fedora containers anymore diff --git a/root/help.1 b/root/help.1 new file mode 100644 index 0000000..5296dbf --- /dev/null +++ b/root/help.1 @@ -0,0 +1,268 @@ +.TH "RUBY-23-RHEL7" "1" " Container Image Pages" "Red Hat" "April 07, 2017" "" + + +.SH Ruby 2.4 platform for building and running applications +.PP +This repository contains the source for building various versions of +the Ruby application as a reproducible Docker image using + +\[la]https://github.com/openshift/source-to-image\[ra]. +Users can choose between RHEL and CentOS based builder images. +The resulting image can be run using +\[la]http://docker.io\[ra]. + +.SH Usage +.PP +To build a simple +\[la]https://github.com/openshift/sti-ruby/tree/master/2.3/test/puma-test-app\[ra] application +using standalone +\[la]https://github.com/openshift/source-to-image\[ra] and then run the +resulting image with +\[la]http://docker.io\[ra] execute: +.IP \n+[step] + +\item +.PP +\fBFor RHEL based image\fP +\fB\fC +$ s2i build https://github.com/openshift/sti\-ruby.git \-\-context\-dir=2.3/test/puma\-test\-app/ rhscl/ruby\-23\-rhel7 ruby\-sample\-app +$ docker run \-p 8080:8080 ruby\-sample\-app +\fR +\item +.PP +\fBFor CentOS based image\fP +\fB\fC +$ s2i build https://github.com/openshift/sti\-ruby.git \-\-context\-dir=2.3/test/puma\-test\-app/ centos/ruby\-23\-centos7 ruby\-sample\-app +$ docker run \-p 8080:8080 ruby\-sample\-app +\fR +.PP +\fBAccessing the application:\fP + +.PP +.RS + +.nf +$ curl 127.0.0.1:8080 + +.fi +.RE + +.SH Repository organization.IP \n+[step] + +\item +.PP +\fB\fB\fC\fR\fP.IP \n+[step] + +\item +.PP +\fBDockerfile\fP +.PP +CentOS based Dockerfile. +\item +.PP +\fBDockerfile.rhel7\fP +.PP +RHEL based Dockerfile. In order to perform build or test actions on this +Dockerfile you need to run the action on a properly subscribed RHEL machine. +\item +.PP +\fB\fB\fCs2i/bin/\fR\fP +.PP +This folder contains scripts that are run by +\[la]https://github.com/openshift/source-to-image\[ra]: +.IP \n+[step] + +\item +.PP +\fBassemble\fP +.PP +Used to install the sources into the location where the application +will be run and prepare the application for deployment (eg. installing +modules using bundler, etc.) +\item +.PP +\fBrun\fP +.PP +This script is responsible for running the application by using the +application web server. +\item +.PP +\fBusage\fP* +.PP +This script prints the usage of this image. +\item +.PP +\fB\fB\fCcontrib/\fR\fP +.PP +This folder contains a file with commonly used modules. +\item +.PP +\fB\fB\fCtest/\fR\fP +.PP +This folder contains a +\[la]https://github.com/openshift/source-to-image\[ra] +test framework with a simple Rack server. +.IP \n+[step] + +\item +.PP +\fB\fB\fCpuma\-test\-app/\fR\fP +.PP +Simple Puma web server used for testing purposes by the +\[la]https://github.com/openshift/source-to-image\[ra] test framework. +\item +.PP +\fB\fB\fCrack\-test\-app/\fR\fP +.PP +Simple Rack web server used for testing purposes by the +\[la]https://github.com/openshift/source-to-image\[ra] test framework. +\item +.PP +\fBrun\fP +.PP +Script that runs the +\[la]https://github.com/openshift/source-to-image\[ra] test framework. +.SH Environment variables +.PP +To set these environment variables, you can place them as a key value pair into a \fB\fC.sti/environment\fR +file inside your source code repository. +.IP \n+[step] + +\item +.PP +\fBRACK\_ENV\fP +.PP +This variable specifies the environment where the Ruby application will be deployed (unless overwritten) \- \fB\fCproduction\fR, \fB\fCdevelopment\fR, \fB\fCtest\fR. +Each level has different behaviors in terms of logging verbosity, error pages, ruby gem installation, etc. + +.PP +\fBNote\fP: Application assets will be compiled only if the \fB\fCRACK\_ENV\fR is set to \fB\fCproduction\fR +\item +.PP +\fBDISABLE\_ASSET\_COMPILATION\fP +.PP +This variable set to \fB\fCtrue\fR indicates that the asset compilation process will be skipped. Since this only takes place +when the application is run in the \fB\fCproduction\fR environment, it should only be used when assets are already compiled. +\item +.PP +\fBPUMA\_MIN\_THREADS\fP, \fBPUMA\_MAX\_THREADS\fP +.PP +These variables indicate the minimum and maximum threads that will be available in +\[la]https://github.com/puma/puma\[ra]'s thread pool. +\item +.PP +\fBPUMA\_WORKERS\fP +.PP +This variable indicate the number of worker processes that will be launched. See documentation on Puma's +\[la]https://github.com/puma/puma#clustered-mode\[ra]. +.SH Hot deploy +.PP +In order to dynamically pick up changes made in your application source code, you need to make following steps: +.IP \n+[step] + +\item +.PP +\fBFor Ruby on Rails applications\fP +.PP +Run the built Rails image with the \fB\fCRAILS\_ENV=development\fR environment variable passed to the +\[la]http://docker.io\[ra] \fB\fC\-e\fR run flag: +\fB\fC +$ docker run \-e RAILS\_ENV=development \-p 8080:8080 rails\-app +\fR +\item +.PP +\fBFor other types of Ruby applications (Sinatra, Padrino, etc.)\fP +.PP +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: +.IP \n+[step] + +\item +\[la]https://github.com/rtomayko/shotgun\[ra] +\item +\[la]https://github.com/alexch/rerun\[ra] +\item +\[la]https://github.com/johnbintz/rack-livereload\[ra] +.PP +Please note that in order to be able to run your application in development mode, you need to modify the +\[la]https://github.com/openshift/source-to-image#anatomy-of-a-builder-image\[ra], so the web server is launched by the chosen gem, which checks for changes in the source code. + +.PP +After you built your application image with your version of +\[la]https://github.com/openshift/source-to-image#anatomy-of-a-builder-image\[ra], run the image with the RACK\_ENV=development environment variable passed to the +\[la]http://docker.io\[ra] \-e run flag: +\fB\fC +$ docker run \-e RACK\_ENV=development \-p 8080:8080 sinatra\-app +\fR +.PP +To change your source code in running container, use Docker's +\[la]http://docker.io\[ra] command: + +.PP +.RS + +.nf +docker exec \-it /bin/bash + +.fi +.RE + +.PP +After you +\[la]http://docker.io\[ra] into the running container, your current +directory is set to \fB\fC/opt/app\-root/src\fR, where the source code is located. + +.SH Performance tuning +.PP +You can tune the number of threads per worker using the +\fB\fCPUMA\_MIN\_THREADS\fR and \fB\fCPUMA\_MAX\_THREADS\fR environment variables. +Additionally, the number of worker processes is determined by the number of CPU +cores that the container has available, as recommended by + +\[la]https://github.com/puma/puma\[ra]'s documentation. This is determined using +the cgroup +\[la]https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt\[ra] +subsystem. You can specify the cores that the container is allowed to use by passing +the \fB\fC\-\-cpuset\-cpus\fR parameter to the +\[la]http://docker.io\[ra] run command: + +.PP +.RS + +.nf +$ docker run \-e PUMA\_MAX\_THREADS=32 \-\-cpuset\-cpus='0\-2,3,5' \-p 8080:8080 sinatra\-app + +.fi +.RE + +.PP +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: + +.PP +.RS + +.nf +50 + 15 * WORKERS + 0.125 * WORKERS * PUMA\_MAX\_THREADS + +.fi +.RE + +.PP +You can specify a memory limit using the \fB\fC\-\-memory\fR flag: + +.PP +.RS + +.nf +$ docker run \-e PUMA\_MAX\_THREADS=32 \-\-memory=300m \-p 8080:8080 sinatra\-app + +.fi +.RE + +.PP +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 \fB\fCPUMA\_WORKERS\fR. diff --git a/s2i/bin/assemble b/s2i/bin/assemble new file mode 100755 index 0000000..16145ea --- /dev/null +++ b/s2i/bin/assemble @@ -0,0 +1,63 @@ +#!/bin/bash + +function rake_assets_precompile() { + [[ "$DISABLE_ASSET_COMPILATION" == "true" ]] && return + [ ! -f Gemfile ] && return + [ ! -f Rakefile ] && return + ! grep " rails " Gemfile.lock >/dev/null && return + ! grep " execjs " 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"} + +echo "---> Installing application source ..." +cp -Rf /tmp/src/. ./ + +echo "---> Building your Ruby application from source ..." +if [ -f Gemfile ]; then + ADDTL_BUNDLE_ARGS="" + 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 + + 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 ./ + +# Make the ./tmp folder world writeable as Rails or other frameworks might use +# it to store temporary data (uploads/cache/sessions/etcd). +# The ./db folder has to be writeable as well because when Rails complete the +# migration it writes the schema version into ./db/schema.db +set +e +[[ -d ./tmp ]] && chgrp -R 0 ./tmp && chmod -R g+rw ./tmp +[[ -d ./db ]] && chgrp -R 0 ./db && chmod -R g+rw ./db +set -e 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..56189c2 --- /dev/null +++ b/s2i/bin/usage @@ -0,0 +1,18 @@ +#!/bin/sh + +DISTRO=`cat /etc/*-release | grep ^ID= | grep -Po '".*?"' | tr -d '"'` +NAMESPACE=centos +[[ $DISTRO =~ rhel* ]] && NAMESPACE=rhscl + +cat <= 1.1, < 2.0) + rack (1.5.2) + rack-protection (1.5.0) + rack + sinatra (1.4.5) + rack (~> 1.4) + rack-protection (~> 1.4) + tilt (~> 1.3, >= 1.3.4) + tilt (1.4.1) + +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 100755 index 0000000..bc5e6c5 --- /dev/null +++ b/test/rack-test-app/Gemfile.lock @@ -0,0 +1,17 @@ +GEM + remote: https://rubygems.org/ + specs: + rack (1.5.2) + rack-protection (1.5.0) + rack + sinatra (1.4.5) + rack (~> 1.4) + rack-protection (~> 1.4) + tilt (~> 1.3, >= 1.3.4) + tilt (1.4.1) + +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/ruby-hello-world b/test/ruby-hello-world new file mode 160000 index 0000000..e79d887 --- /dev/null +++ b/test/ruby-hello-world @@ -0,0 +1 @@ +Subproject commit e79d8870be808a7abb4ab304e94c8bee69d909c6 diff --git a/test/run b/test/run new file mode 100755 index 0000000..8b34c75 --- /dev/null +++ b/test/run @@ -0,0 +1,191 @@ +#!/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-openshift/ruby-23-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}/..) + +# 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() { + s2i build ${s2i_args} file://${test_dir}/${1}-test-app ${IMAGE_NAME} ${IMAGE_NAME}-testapp +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + exit 1 + fi + # TODO: S2I build require the application is a valid 'GIT' repository, we + # should remove this restriction in the future when a file:// is used. + info "Build the test application image" + pushd ${test_dir}/${1}-test-app >/dev/null + git init + git config user.email "build@localhost" && git config user.name "builder" + git add -A && git commit -m "Sample commit" + popd >/dev/null +} + +run_test_application() { + docker run --user=100001 --rm --cidfile=${cid_file} -p ${test_port} ${IMAGE_NAME}-testapp +} + +cleanup() { + 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 + rm -rf ${test_dir}/${1}-test-app/.git + if [[ $1 == "db" ]]; then + rm -rf ${test_dir}/db-test-app + fi +} + +check_result() { + local result="$1" + if [[ "$result" != "0" ]]; then + info "TEST FAILED (${result})" + cleanup + exit $result + fi +} + +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + local result=1 + info "Waiting for application container to start" + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && break + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done +} + +test_s2i_usage() { + info "Testing 's2i usage'" + s2i usage ${s2i_args} ${IMAGE_NAME} &>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage" + docker run ${IMAGE_NAME} &>/dev/null +} + +test_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 + result=0 + fi + break + fi + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return $result +} + +test_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 +} + +pushd ${test_dir} +git clone git://github.com/openshift/ruby-hello-world +mv 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="--force-pull=false" + + prepare ${server} + run_s2i_build ${server} + check_result $? + + # Verify the 'usage' script is working properly when running the base image with 's2i usage ...' + test_s2i_usage + check_result $? + + # Verify the 'usage' script is working properly when running the base image with 'docker run ...' + test_docker_run_usage + check_result $? + + # Verify that the HTTP connection can be established to test application container + run_test_application & + + # Wait for the container to write it's CID file + wait_for_cid + + test_connection + check_result $? + + test_scl_usage "ruby --version" "ruby 2.3.0" + check_result $? + + info "All tests for the ${server}-test-app finished successfully." + cleanup ${server} +done + +info "All tests finished successfully."