Compare commits

..

1 commit

Author SHA1 Message Date
Marek Skalický
8d712db213
Pull changes from upstream repository. 2018-11-05 16:09:13 +00:00
31 changed files with 4511 additions and 1 deletions

0
.gitignore vendored Normal file
View file

1
7.1 Symbolic link
View file

@ -0,0 +1 @@
.

87
Dockerfile Normal file
View file

@ -0,0 +1,87 @@
FROM registry.fedoraproject.org/f27/s2i-base:latest
# This image provides an Apache+PHP environment for running PHP
# applications.
EXPOSE 8080
EXPOSE 8443
ENV PHP_VERSION=7.1 \
PATH=$PATH:/usr/bin
ENV SUMMARY="Platform for building and running PHP $PHP_VERSION applications" \
DESCRIPTION="PHP $PHP_VERSION available as container is a base platform for \
building and running various PHP $PHP_VERSION applications and frameworks. \
PHP is an HTML-embedded scripting language. PHP attempts to make it easy for developers \
to write dynamically generated web pages. PHP also offers built-in database integration \
for several commercial and non-commercial database management systems, so writing \
a database-enabled webpage with PHP is fairly simple. The most common use of PHP coding \
is probably as a replacement for CGI scripts."
ENV NAME=php \
VERSION=0 \
RELEASE=1 \
ARCH=x86_64
LABEL summary="$SUMMARY" \
description="$DESCRIPTION" \
io.k8s.description="$DESCRIPTION" \
io.k8s.display-name="Apache 2.4 with PHP 7.1" \
io.openshift.expose-services="8080:http" \
io.openshift.tags="builder,php,php71,rh-php71" \
name="$FGC/$NAME" \
com.redhat.component="$NAME" \
version="$VERSION" \
usage="s2i build https://github.com/sclorg/s2i-php-container.git --context-dir=/7.1/test/test-app $FGC/$NAME sample-server" \
maintainer="SoftwareCollections.org <sclorg@redhat.com>"
# Install Apache httpd and PHP
RUN INSTALL_PKGS="php php-mysqlnd php-bcmath \
php-gd php-intl php-ldap php-mbstring php-pdo \
php-process php-soap php-opcache php-xml \
php-gmp php-pecl-apcu mod_ssl hostname" && \
yum install -y --setopt=tsflags=nodocs $INSTALL_PKGS --nogpgcheck && \
rpm -V $INSTALL_PKGS && \
yum clean all -y
ENV PHP_CONTAINER_SCRIPTS_PATH=/usr/share/container-scripts/php/ \
APP_DATA=${APP_ROOT}/src \
PHP_DEFAULT_INCLUDE_PATH=/usr/share/pear \
PHP_SYSCONF_PATH=/etc/ \
PHP_HTTPD_CONF_FILE=php.conf \
HTTPD_CONFIGURATION_PATH=${APP_ROOT}/etc/conf.d \
HTTPD_MAIN_CONF_PATH=/etc/httpd/conf \
HTTPD_MAIN_CONF_D_PATH=/etc/httpd/conf.d \
HTTPD_VAR_RUN=/var/run/httpd \
HTTPD_DATA_PATH=/var/www \
HTTPD_DATA_ORIG_PATH=/var/www \
HTTPD_VAR_PATH=/var
# 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/ /
# Reset permissions of filesystem to default values
# Generate SSL certs and reset permissions of filesystem to default values
RUN /usr/libexec/httpd-ssl-gencerts && \
/usr/libexec/container-setup && rpm-file-permissions
# Fedora uses by default 'event' MPM module
# switch to 'prefork' to provide same user experience as with RHEL image
# Code taken from 'config_mpm()' function in sclorg/httpd-container repo
ENV HTTPD_MPM=prefork \
HTTPD_MAIN_CONF_MODULES_D_PATH=/etc/httpd/conf.modules.d
RUN if [ -v HTTPD_MPM -a -f ${HTTPD_MAIN_CONF_MODULES_D_PATH}/00-mpm.conf ]; then \
mpmconf=${HTTPD_MAIN_CONF_MODULES_D_PATH}/00-mpm.conf; \
sed -i -e 's,^LoadModule,#LoadModule,' ${mpmconf}; \
sed -i -e "/LoadModule mpm_${HTTPD_MPM}/s,^#LoadModule,LoadModule," ${mpmconf}; \
echo "---> Set MPM to ${HTTPD_MPM} in ${mpmconf}"; \
fi
USER 1001
# Set the default CMD to print the usage of the language image
CMD $STI_SCRIPTS_PATH/usage

1
Dockerfile.fedora Symbolic link
View file

@ -0,0 +1 @@
Dockerfile

209
README.md Normal file
View file

@ -0,0 +1,209 @@
PHP 7.1 Docker image
================
This container image includes PHP 7.1 as a [S2I](https://github.com/openshift/source-to-image) base image for your PHP 7.1 applications.
Users can choose between RHEL and CentOS based builder images.
The RHEL image is available in the [Red Hat Container Catalog](https://access.redhat.com/containers/#/registry.access.redhat.com/rhscl/php-71-rhel7)
as registry.access.redhat.com/rhscl/php-71-rhel7.
The CentOS image is then available on [Docker Hub](https://hub.docker.com/r/centos/php-71-centos7/)
as centos/php-71-centos7.
The resulting image can be run using [Docker](http://docker.io).
Description
-----------
PHP 7.1 available as container is a base platform for
building and running various PHP 7.1 applications and frameworks.
PHP is an HTML-embedded scripting language. PHP attempts to make it easy for developers
to write dynamically generated web pages. PHP also offers built-in database integration
for several commercial and non-commercial database management systems, so writing
a database-enabled webpage with PHP is fairly simple. The most common use of PHP coding
is probably as a replacement for CGI scripts.
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
---------------------
To build a simple [php-test-app](https://github.com/sclorg/s2i-php-container/tree/master/7.1/test/test-app) application
using standalone [S2I](https://github.com/openshift/source-to-image) and then run the
resulting image with [Docker](http://docker.io) execute:
* **For RHEL based image**
```
$ s2i build https://github.com/sclorg/s2i-php-container.git --context-dir=7.1/test/test-app rhscl/php-71-rhel7 php-test-app
$ docker run -p 8080:8080 php-test-app
```
* **For CentOS based image**
```
$ s2i build https://github.com/sclorg/s2i-php-container.git --context-dir=7.1/test/test-app centos/php-71-centos7 php-test-app
$ docker run -p 8080:8080 php-test-app
```
**Accessing the application:**
```
$ curl 127.0.0.1:8080
```
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.
The following environment variables set their equivalent property value in the php.ini file:
* **ERROR_REPORTING**
* Informs PHP of which errors, warnings and notices you would like it to take action for
* Default: E_ALL & ~E_NOTICE
* **DISPLAY_ERRORS**
* Controls whether or not and where PHP will output errors, notices and warnings
* Default: ON
* **DISPLAY_STARTUP_ERRORS**
* Cause display errors which occur during PHP's startup sequence to be handled separately from display errors
* Default: OFF
* **TRACK_ERRORS**
* Store the last error/warning message in $php_errormsg (boolean)
* Default: OFF
* **HTML_ERRORS**
* Link errors to documentation related to the error
* Default: ON
* **INCLUDE_PATH**
* Path for PHP source files
* Default: .:/opt/app-root/src:/opt/rh/rh-php71/root/usr/share/pear
* **PHP_MEMORY_LIMIT**
* Memory Limit
* Default: 128M
* **SESSION_NAME**
* Name of the session
* Default: PHPSESSID
* **SESSION_HANDLER**
* Method for saving sessions
* Default: files
* **SESSION_PATH**
* Location for session data files
* Default: /tmp/sessions
* **SESSION_COOKIE_DOMAIN**
* The domain for which the cookie is valid.
* Default:
* **SESSION_COOKIE_HTTPONLY**
* Whether or not to add the httpOnly flag to the cookie
* Default: 0
* **SESSION_COOKIE_SECURE**
* Specifies whether cookies should only be sent over secure connections.
* Default: Off
* **SHORT_OPEN_TAG**
* Determines whether or not PHP will recognize code between <? and ?> tags
* Default: OFF
* **DOCUMENTROOT**
* Path that defines the DocumentRoot for your application (ie. /public)
* Default: /
The following environment variables set their equivalent property value in the opcache.ini file:
* **OPCACHE_MEMORY_CONSUMPTION**
* The OPcache shared memory storage size in megabytes
* Default: 128
* **OPCACHE_REVALIDATE_FREQ**
* How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.
* Default: 2
You can also override the entire directory used to load the PHP configuration by setting:
* **PHPRC**
* Sets the path to the php.ini file
* **PHP_INI_SCAN_DIR**
* Path to scan for additional ini configuration files
You can override the Apache [MPM prefork](https://httpd.apache.org/docs/2.4/mod/mpm_common.html)
settings to increase the performance for of the PHP application. In case you set
the Cgroup limits in Docker, the image will attempt to automatically set the
optimal values. You can override this at any time by specifying the values
yourself:
* **HTTPD_START_SERVERS**
* The [StartServers](https://httpd.apache.org/docs/2.4/mod/mpm_common.html#startservers)
directive sets the number of child server processes created on startup.
* Default: 8
* **HTTPD_MAX_REQUEST_WORKERS**
* The [MaxRequestWorkers](https://httpd.apache.org/docs/2.4/mod/mpm_common.html#maxrequestworkers)
directive sets the limit on the number of simultaneous requests that will be served.
* `MaxRequestWorkers` was called `MaxClients` before version httpd 2.3.13.
* Default: 256 (this is automatically tuned by setting Cgroup limits for the container using this formula:
`TOTAL_MEMORY / 15MB`. The 15MB is average size of a single httpd process.
You can use a custom composer repository mirror URL to download packages instead of the default 'packagist.org':
* **COMPOSER_MIRROR**
* Adds a custom composer repository mirror URL to composer configuration. Note: This only affects packages listed in composer.json.
* **COMPOSER_INSTALLER**
* Overrides the default URL for downloading Composer of https://getcomposer.org/installer. Useful in disconnected environments.
* **COMPOSER_ARGS**
* Adds extra arguments to the `composer install` command line (for example `--no-dev`).
Source repository layout
------------------------
You do not need to change anything in your existing PHP project's repository.
However, if these files exist they will affect the behavior of the build process:
* **composer.json**
List of dependencies to be installed with `composer`. The format is documented
[here](https://getcomposer.org/doc/04-schema.md).
* **.htaccess**
In case the **DocumentRoot** of the application is nested within the source directory `/opt/app-root/src`,
users can provide their own Apache **.htaccess** file. This allows the overriding of Apache's behavior and
specifies how application requests should be handled. The **.htaccess** file needs to be located at the root
of the application source.
Hot deploy
---------------------
In order to immediately pick up changes made in your application source code, you need to run your built image with the `OPCACHE_REVALIDATE_FREQ=0` environment variable passed to the [Docker](http://docker.io) `-e` run flag:
```
$ docker run -e OPCACHE_REVALIDATE_FREQ=0 -p 8080:8080 php-app
```
To change your source code in running container, use Docker's [exec](http://docker.io) command:
```
docker exec -it <CONTAINER_ID> /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.
Extending image
---------------
Not only content, but also startup scripts and configuration of the image can
be extended using [source-to-image](https://github.com/openshift/source-to-image).
The structure of the application can look like this:
| Folder name | Description |
|-------------------|----------------------------|
| `./httpd-cfg` | Can contain additional Apache configuration files (`*.conf`)|
| `./httpd-ssl` | Can contain own SSL certificate (in `certs/` subdirectory) and key (in `private/` subdirectory)|
| `./php-pre-start`| Can contain shell scripts (`*.sh`) that are sourced before `httpd` is started|
| `./php-post-assemble`| Can contain shell scripts (`*.sh`) that are sourced at the end of `assemble` script|
| `./` | Application source code |
See also
--------
Dockerfile and other sources are available on https://github.com/sclorg/s2i-php-container.
In that repository you also can find another versions of PHP environment Dockerfiles.
Dockerfile for CentOS is called Dockerfile, Dockerfile for RHEL is called Dockerfile.rhel7.
Security Implications
---------------------
-p 8080:8080
Opens container port 8080 and maps it to the same port on the Host.

View file

@ -1 +0,0 @@
Container not being built in Fedora infrastructure for years, check gitlab.com/sclorg instead

1
help.md Symbolic link
View file

@ -0,0 +1 @@
README.md

294
root/help.1 Normal file
View file

@ -0,0 +1,294 @@
.TH PHP 7.1 Docker image
.PP
This container image includes PHP 7.1 as a S2I
\[la]https://github.com/openshift/source-to-image\[ra] base image for your PHP 7.1 applications.
Users can choose between RHEL and CentOS based builder images.
The RHEL image is available in the Red Hat Container Catalog
\[la]https://access.redhat.com/containers/#/registry.access.redhat.com/rhscl/php-71-rhel7\[ra]
as registry.access.redhat.com/rhscl/php\-71\-rhel7.
The CentOS image is then available on Docker Hub
\[la]https://hub.docker.com/r/centos/php-71-centos7/\[ra]
as centos/php\-71\-centos7.
The resulting image can be run using Docker
\[la]http://docker.io\[ra]\&.
.SH Description
.PP
PHP 7.1 available as container is a base platform for
building and running various PHP 7.1 applications and frameworks.
PHP is an HTML\-embedded scripting language. PHP attempts to make it easy for developers
to write dynamically generated web pages. PHP also offers built\-in database integration
for several commercial and non\-commercial database management systems, so writing
a database\-enabled webpage with PHP is fairly simple. The most common use of PHP coding
is probably as a replacement for CGI scripts.
.PP
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.
.SH Usage
.PP
To build a simple php\-test\-app
\[la]https://github.com/sclorg/s2i-php-container/tree/master/7.1/test/test-app\[ra] application
using standalone S2I
\[la]https://github.com/openshift/source-to-image\[ra] and then run the
resulting image with Docker
\[la]http://docker.io\[ra] execute:
.IP \(bu 2
.PP
\fBFor RHEL based image\fP
.PP
.RS
.nf
$ s2i build https://github.com/sclorg/s2i\-php\-container.git \-\-context\-dir=7.1/test/test\-app rhscl/php\-71\-rhel7 php\-test\-app
$ docker run \-p 8080:8080 php\-test\-app
.fi
.RE
.IP \(bu 2
.PP
\fBFor CentOS based image\fP
.PP
.RS
.nf
$ s2i build https://github.com/sclorg/s2i\-php\-container.git \-\-context\-dir=7.1/test/test\-app centos/php\-71\-centos7 php\-test\-app
$ docker run \-p 8080:8080 php\-test\-app
.fi
.RE
.PP
\fBAccessing the application:\fP
.PP
.RS
.nf
$ curl 127.0.0.1:8080
.fi
.RE
.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.
.PP
The following environment variables set their equivalent property value in the php.ini file:
* \fBERROR\_REPORTING\fP
* Informs PHP of which errors, warnings and notices you would like it to take action for
* Default: E\_ALL \& \~E\_NOTICE
* \fBDISPLAY\_ERRORS\fP
* Controls whether or not and where PHP will output errors, notices and warnings
* Default: ON
* \fBDISPLAY\_STARTUP\_ERRORS\fP
* Cause display errors which occur during PHP's startup sequence to be handled separately from display errors
* Default: OFF
* \fBTRACK\_ERRORS\fP
* Store the last error/warning message in $php\_errormsg (boolean)
* Default: OFF
* \fBHTML\_ERRORS\fP
* Link errors to documentation related to the error
* Default: ON
* \fBINCLUDE\_PATH\fP
* Path for PHP source files
* Default: .:/opt/app\-root/src:/opt/rh/rh\-php71/root/usr/share/pear
* \fBPHP\_MEMORY\_LIMIT\fP
* Memory Limit
* Default: 128M
* \fBSESSION\_NAME\fP
* Name of the session
* Default: PHPSESSID
* \fBSESSION\_HANDLER\fP
* Method for saving sessions
* Default: files
* \fBSESSION\_PATH\fP
* Location for session data files
* Default: /tmp/sessions
* \fBSESSION\_COOKIE\_DOMAIN\fP
* The domain for which the cookie is valid.
* Default:
* \fBSESSION\_COOKIE\_HTTPONLY\fP
* Whether or not to add the httpOnly flag to the cookie
* Default: 0
* \fBSESSION\_COOKIE\_SECURE\fP
* Specifies whether cookies should only be sent over secure connections.
* Default: Off
* \fBSHORT\_OPEN\_TAG\fP
* Determines whether or not PHP will recognize code between <? and ?> tags
* Default: OFF
* \fBDOCUMENTROOT\fP
* Path that defines the DocumentRoot for your application (ie. /public)
* Default: /
.PP
The following environment variables set their equivalent property value in the opcache.ini file:
* \fBOPCACHE\_MEMORY\_CONSUMPTION\fP
* The OPcache shared memory storage size in megabytes
* Default: 128
* \fBOPCACHE\_REVALIDATE\_FREQ\fP
* How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.
* Default: 2
.PP
You can also override the entire directory used to load the PHP configuration by setting:
* \fBPHPRC\fP
* Sets the path to the php.ini file
* \fBPHP\_INI\_SCAN\_DIR\fP
* Path to scan for additional ini configuration files
.PP
You can override the Apache MPM prefork
\[la]https://httpd.apache.org/docs/2.4/mod/mpm_common.html\[ra]
settings to increase the performance for of the PHP application. In case you set
the Cgroup limits in Docker, the image will attempt to automatically set the
optimal values. You can override this at any time by specifying the values
yourself:
.IP \(bu 2
\fBHTTPD\_START\_SERVERS\fP
.IP \(bu 2
The StartServers
\[la]https://httpd.apache.org/docs/2.4/mod/mpm_common.html#startservers\[ra]
directive sets the number of child server processes created on startup.
.IP \(bu 2
Default: 8
.IP \(bu 2
\fBHTTPD\_MAX\_REQUEST\_WORKERS\fP
.IP \(bu 2
The MaxRequestWorkers
\[la]https://httpd.apache.org/docs/2.4/mod/mpm_common.html#maxrequestworkers\[ra]
directive sets the limit on the number of simultaneous requests that will be served.
.IP \(bu 2
\fB\fCMaxRequestWorkers\fR was called \fB\fCMaxClients\fR before version httpd 2.3.13.
.IP \(bu 2
Default: 256 (this is automatically tuned by setting Cgroup limits for the container using this formula:
\fB\fCTOTAL\_MEMORY / 15MB\fR\&. The 15MB is average size of a single httpd process.
.PP
You can use a custom composer repository mirror URL to download packages instead of the default 'packagist.org':
.PP
.RS
.nf
* **COMPOSER\_MIRROR**
* Adds a custom composer repository mirror URL to composer configuration. Note: This only affects packages listed in composer.json.
* **COMPOSER\_INSTALLER**
* Overrides the default URL for downloading Composer of https://getcomposer.org/installer. Useful in disconnected environments.
* **COMPOSER\_ARGS**
* Adds extra arguments to the `composer install` command line (for example `\-\-no\-dev`).
.fi
.RE
.SH Source repository layout
.PP
You do not need to change anything in your existing PHP project's repository.
However, if these files exist they will affect the behavior of the build process:
.IP \(bu 2
\fBcomposer.json\fP
.PP
List of dependencies to be installed with \fB\fCcomposer\fR\&. The format is documented
here
\[la]https://getcomposer.org/doc/04-schema.md\[ra]\&.
.IP \(bu 2
\fB\&.htaccess\fP
.PP
In case the \fBDocumentRoot\fP of the application is nested within the source directory \fB\fC/opt/app\-root/src\fR,
users can provide their own Apache \fB\&.htaccess\fP file. This allows the overriding of Apache's behavior and
specifies how application requests should be handled. The \fB\&.htaccess\fP file needs to be located at the root
of the application source.
.SH Hot deploy
.PP
In order to immediately pick up changes made in your application source code, you need to run your built image with the \fB\fCOPCACHE\_REVALIDATE\_FREQ=0\fR environment variable passed to the Docker
\[la]http://docker.io\[ra] \fB\fC\-e\fR run flag:
.PP
.RS
.nf
$ docker run \-e OPCACHE\_REVALIDATE\_FREQ=0 \-p 8080:8080 php\-app
.fi
.RE
.PP
To change your source code in running container, use Docker's exec
\[la]http://docker.io\[ra] command:
.PP
.RS
.nf
docker exec \-it <CONTAINER\_ID> /bin/bash
.fi
.RE
.PP
After you Docker exec
\[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 Extending image
.PP
Not only content, but also startup scripts and configuration of the image can
be extended using source\-to\-image
\[la]https://github.com/openshift/source-to-image\[ra]\&.
.PP
The structure of the application can look like this:
.TS
allbox;
l l
l l .
\fB\fCFolder name\fR \fB\fCDescription\fR
\fB\fC\&./httpd\-\&cfg\fR T{
Can contain additional Apache configuration files (\fB\fC*.conf\fR)
T}
\fB\fC\&./httpd\-\&ssl\fR T{
Can contain own SSL certificate (in \fB\fCcerts/\fR subdirectory) and key (in \fB\fCprivate/\fR subdirectory)
T}
T{
\fB\fC\&./php\-\&pre\-\&start\fR
T} T{
Can contain shell scripts (\fB\fC*.sh\fR) that are sourced before \fB\fChttpd\fR is started
T}
T{
\fB\fC\&./php\-\&post\-\&assemble\fR
T} T{
Can contain shell scripts (\fB\fC*.sh\fR) that are sourced at the end of \fB\fCassemble\fR script
T}
\fB\fC\&./\fR Application source code
.TE
.SH See also
.PP
Dockerfile and other sources are available on
\[la]https://github.com/sclorg/s2i-php-container\[ra]\&.
In that repository you also can find another versions of PHP environment Dockerfiles.
Dockerfile for CentOS is called Dockerfile, Dockerfile for RHEL is called Dockerfile.rhel7.
.SH Security Implications
.PP
\-p 8080:8080
.PP
.RS
.nf
Opens container port 8080 and maps it to the same port on the Host.
.fi
.RE

View file

@ -0,0 +1,113 @@
; Enable Zend OPcache extension module
zend_extension=opcache.so
; Determines if Zend OPCache is enabled
opcache.enable=1
; Determines if Zend OPCache is enabled for the CLI version of PHP
;opcache.enable_cli=0
; The OPcache shared memory storage size.
opcache.memory_consumption=${OPCACHE_MEMORY_CONSUMPTION}
; The amount of memory for interned strings in Mbytes.
opcache.interned_strings_buffer=8
; The maximum number of keys (scripts) in the OPcache hash table.
; Only numbers between 200 and 100000 are allowed.
opcache.max_accelerated_files=4000
; The maximum percentage of "wasted" memory until a restart is scheduled.
;opcache.max_wasted_percentage=5
; When this directive is enabled, the OPcache appends the current working
; directory to the script key, thus eliminating possible collisions between
; files with the same name (basename). Disabling the directive improves
; performance, but may break existing applications.
;opcache.use_cwd=1
; When disabled, you must reset the OPcache manually or restart the
; webserver for changes to the filesystem to take effect.
;opcache.validate_timestamps=1
; How often (in seconds) to check file timestamps for changes to the shared
; memory storage allocation. ("1" means validate once per second, but only
; once per request. "0" means always validate)
opcache.revalidate_freq=${OPCACHE_REVALIDATE_FREQ}
; Enables or disables file search in include_path optimization
;opcache.revalidate_path=0
; If disabled, all PHPDoc comments are dropped from the code to reduce the
; size of the optimized code.
;opcache.save_comments=1
; If enabled, a fast shutdown sequence is used for the accelerated code
;opcache.fast_shutdown=0
; Allow file existence override (file_exists, etc.) performance feature.
;opcache.enable_file_override=0
; A bitmask, where each bit enables or disables the appropriate OPcache
; passes
;opcache.optimization_level=0xffffffff
;opcache.inherited_hack=1
;opcache.dups_fix=0
; The location of the OPcache blacklist file (wildcards allowed).
; Each OPcache blacklist file is a text file that holds the names of files
; that should not be accelerated.
opcache.blacklist_filename=${PHP_SYSCONF_PATH}/php.d/opcache*.blacklist
; Allows exclusion of large files from being cached. By default all files
; are cached.
opcache.max_file_size=1M
; Check the cache checksum each N requests.
; The default value of "0" means that the checks are disabled.
;opcache.consistency_checks=0
; How long to wait (in seconds) for a scheduled restart to begin if the cache
; is not being accessed.
;opcache.force_restart_timeout=180
; OPcache error_log file name. Empty string assumes "stderr".
;opcache.error_log=
; All OPcache errors go to the Web server log.
; By default, only fatal errors (level 0) or errors (level 1) are logged.
; You can also enable warnings (level 2), info messages (level 3) or
; debug messages (level 4).
;opcache.log_verbosity_level=1
; Preferred Shared Memory back-end. Leave empty and let the system decide.
;opcache.preferred_memory_model=
; Protect the shared memory from unexpected writing during script execution.
; Useful for internal debugging only.
;opcache.protect_memory=0
; Allows calling OPcache API functions only from PHP scripts which path is
; started from specified string. The default "" means no restriction
;opcache.restrict_api=
; Enables and sets the second level cache directory.
; It should improve performance when SHM memory is full, at server restart or
; SHM reset. The default "" disables file based caching.
; RPM note : file cache directory must be owned by process owner
; for mod_php, see /etc/httpd/conf.d/php.conf
; for php-fpm, see /etc/php-fpm.d/*conf
;opcache.file_cache=
; Enables or disables opcode caching in shared memory.
;opcache.file_cache_only=0
; Enables or disables checksum validation when script loaded from file cache.
;opcache.file_cache_consistency_checks=1
; Enables or disables copying of PHP code (text segment) into HUGE PAGES.
; This should improve performance, but requires appropriate OS configuration.
;opcache.huge_code_pages=0

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
# IMPORTANT: Do not add more content to this file unless you know what you are
# doing. This file is sourced everytime the shell session is opened.
#
# This will make scl collection binaries work out of box.
unset BASH_ENV PROMPT_COMMAND ENV
source scl_source enable ${SCL_ENABLED} httpd24 $NODEJS_SCL

View file

@ -0,0 +1,49 @@
#!/bin/bash
set -e
# In order to drop the root user, we have to make some directories world
# writeable as OpenShift default security model is to run the container under
# random UID.
source ${PHP_CONTAINER_SCRIPTS_PATH}/common.sh
# compatibility symlinks so we hide SCL paths
if [ -v SCL_ENABLED ] ; then
# /opt/rh/httpd24/root/etc/httpd will be symlink to /etc/httpd
mv /opt/rh/httpd24/root/etc/httpd /etc/httpd
ln -s /etc/httpd /opt/rh/httpd24/root/etc/httpd
# /opt/rh/httpd24/root/var/run/httpd will be symlink to /var/run/httpd
mv /opt/rh/httpd24/root/var/run/httpd /var/run/httpd
ln -s /var/run/httpd /opt/rh/httpd24/root/var/run/httpd
# /opt/rh/httpd24/root/var/www will be symlink to /var/www
rm -rf /var/www
mv ${HTTPD_DATA_ORIG_PATH} /var/www
ln -s /var/www ${HTTPD_DATA_ORIG_PATH}
else
rm -f /opt/app-root/etc/scl_enable
fi
mkdir -p ${HTTPD_CONFIGURATION_PATH}
chmod -R a+rwx ${HTTPD_MAIN_CONF_PATH}
chmod -R a+rwx ${HTTPD_MAIN_CONF_D_PATH}
chmod -R ug+r /etc/pki/tls/certs/localhost.crt
chmod -R ug+r /etc/pki/tls/private/localhost.key
chown -R 1001:0 /etc/pki/tls/certs/localhost.crt
chown -R 1001:0 /etc/pki/tls/private/localhost.key
mkdir -p ${APP_ROOT}/etc
chmod -R a+rwx ${APP_ROOT}/etc
chmod -R a+rwx ${HTTPD_VAR_RUN}
chown -R 1001:0 ${APP_ROOT}
mkdir /tmp/sessions
chmod -R a+rwx /tmp/sessions
chown -R 1001:0 /tmp/sessions
chown -R 1001:0 ${HTTPD_DATA_PATH}
chmod -R a+rwx ${PHP_SYSCONF_PATH}
mkdir -p ${PHP_CONTAINER_SCRIPTS_PATH}/pre-init
config_general

View file

@ -0,0 +1,140 @@
config_httpd_conf() {
sed -i "s/^Listen 80/Listen 0.0.0.0:8080/" ${HTTPD_MAIN_CONF_PATH}/httpd.conf
sed -i "s/^User apache/User default/" ${HTTPD_MAIN_CONF_PATH}/httpd.conf
sed -i "s/^Group apache/Group root/" ${HTTPD_MAIN_CONF_PATH}/httpd.conf
sed -i "s%^DocumentRoot \"${HTTPD_DATA_ORIG_PATH}/html\"%#DocumentRoot \"${APP_DATA}\"%" ${HTTPD_MAIN_CONF_PATH}/httpd.conf
sed -i "s%^<Directory \"${HTTPD_DATA_ORIG_PATH}/html\"%<Directory \"${APP_DATA}\"%" ${HTTPD_MAIN_CONF_PATH}/httpd.conf
sed -i "s%^<Directory \"${HTTPD_VAR_PATH}/html\"%<Directory \"${APP_DATA}\"%" ${HTTPD_MAIN_CONF_PATH}/httpd.conf
sed -i "s%^ErrorLog \"logs/error_log\"%ErrorLog \"|/usr/bin/cat\"%" ${HTTPD_MAIN_CONF_PATH}/httpd.conf
sed -i "s%CustomLog \"logs/access_log\"%CustomLog \"|/usr/bin/cat\"%" ${HTTPD_MAIN_CONF_PATH}/httpd.conf
sed -i "151s%AllowOverride None%AllowOverride All%" ${HTTPD_MAIN_CONF_PATH}/httpd.conf
}
config_ssl_conf() {
sed -i -E "s/^Listen 443/Listen 0.0.0.0:8443/" ${HTTPD_MAIN_CONF_D_PATH}/ssl.conf
sed -i -E "s/_default_:443/_default_:8443/" ${HTTPD_MAIN_CONF_D_PATH}/ssl.conf
sed -i -E "s!^(\s*CustomLog)\s+\S+!\1 |/usr/bin/cat!" ${HTTPD_MAIN_CONF_D_PATH}/ssl.conf
sed -i -E "s!^(\s*TransferLog)\s+\S+!\1 |/usr/bin/cat!" ${HTTPD_MAIN_CONF_D_PATH}/ssl.conf
sed -i -E "s!^(\s*ErrorLog)\s+\S+!\1 |/usr/bin/cat!" ${HTTPD_MAIN_CONF_D_PATH}/ssl.conf
}
config_general() {
config_httpd_conf
config_ssl_conf
sed -i '/php_value session.save_/d' ${HTTPD_MAIN_CONF_D_PATH}/${PHP_HTTPD_CONF_FILE}
head -n151 ${HTTPD_MAIN_CONF_PATH}/httpd.conf | tail -n1 | grep "AllowOverride All" || exit 1
echo "IncludeOptional ${APP_ROOT}/etc/conf.d/*.conf" >> ${HTTPD_MAIN_CONF_PATH}/httpd.conf
}
function log_info {
echo "---> `date +%T` $@"
}
function log_and_run {
log_info "Running $@"
"$@"
}
function log_volume_info {
CONTAINER_DEBUG=${CONTAINER_DEBUG:-}
if [[ "${CONTAINER_DEBUG,,}" != "true" ]]; then
return
fi
log_info "Volume info for $@:"
set +e
log_and_run mount
while [ $# -gt 0 ]; do
log_and_run ls -alZ $1
shift
done
set -e
}
# get_matched_files finds file for image extending
function get_matched_files() {
local custom_dir default_dir
custom_dir="$1"
default_dir="$2"
files_matched="$3"
find "$default_dir" -maxdepth 1 -type f -name "$files_matched" -printf "%f\n"
[ -d "$custom_dir" ] && find "$custom_dir" -maxdepth 1 -type f -name "$files_matched" -printf "%f\n"
}
# process_extending_files process extending files in $1 and $2 directories
# - source all *.sh files
# (if there are files with same name source only file from $1)
function process_extending_files() {
local custom_dir default_dir
custom_dir=$1
default_dir=$2
while read filename ; do
echo "=> sourcing $filename ..."
# Custom file is prefered
if [ -f $custom_dir/$filename ]; then
source $custom_dir/$filename
elif [ -f $default_dir/$filename ]; then
source $default_dir/$filename
fi
done <<<"$(get_matched_files "$custom_dir" "$default_dir" '*.sh' | sort -u)"
}
# process extending config files in $1 and $2 directories
# - expand variables in *.conf and copy the files into /opt/app-root/etc/httpd.d directory
# (if there are files with same name source only file from $1)
function process_extending_config_files() {
local custom_dir default_dir
custom_dir=$1
default_dir=$2
while read filename ; do
echo "=> sourcing $filename ..."
# Custom file is prefered
if [ -f $custom_dir/$filename ]; then
envsubst < $custom_dir/$filename > ${HTTPD_CONFIGURATION_PATH}/$filename
elif [ -f $default_dir/$filename ]; then
envsubst < $default_dir/$filename > ${HTTPD_CONFIGURATION_PATH}/$filename
fi
done <<<"$(get_matched_files "$custom_dir" "$default_dir" '*.conf' | sort -u)"
}
# Copy config files from application to the location where httpd expects them
# Param sets the directory where to look for files
# This function was taken from httpd container
process_config_files() {
local dir=${1:-.}
if [ -d ${dir}/httpd-cfg ]; then
echo "---> Copying httpd configuration files..."
if [ "$(ls -A ${dir}/httpd-cfg/*.conf)" ]; then
cp -v ${dir}/httpd-cfg/*.conf "${HTTPD_CONFIGURATION_PATH}"/
rm -rf ${dir}/httpd-cfg
fi
fi
}
# Copy SSL files provided in application source
# This function was taken from httpd container
process_ssl_certs() {
local dir=${1:-.}
if [ -d ${dir}/httpd-ssl/private ] && [ -d ${dir}/httpd-ssl/certs ]; then
echo "---> Looking for SSL certs for httpd..."
cp -r ${dir}/httpd-ssl ${APP_ROOT}
local ssl_cert="$(ls -A ${APP_ROOT}/httpd-ssl/certs/*.pem | head -n 1)"
local ssl_private="$(ls -A ${APP_ROOT}/httpd-ssl/private/*.pem | head -n 1)"
if [ -f "${ssl_cert}" ] ; then
# do sed for SSLCertificateFile and SSLCertificateKeyFile
echo "---> Setting SSL cert file for httpd..."
sed -i -e "s|^SSLCertificateFile .*$|SSLCertificateFile ${ssl_cert}|" ${HTTPD_MAIN_CONF_D_PATH}/ssl.conf
if [ -f "${ssl_private}" ]; then
echo "---> Setting SSL key file for httpd..."
sed -i -e "s|^SSLCertificateKeyFile .*$|SSLCertificateKeyFile ${ssl_private}|" ${HTTPD_MAIN_CONF_D_PATH}/ssl.conf
else
echo "---> Removing SSL key file settings for httpd..."
sed -i '/^SSLCertificateKeyFile .*/d' ${HTTPD_MAIN_CONF_D_PATH}/ssl.conf
fi
fi
rm -rf ${dir}/httpd-ssl
fi
}

View file

@ -0,0 +1 @@
DocumentRoot "/opt/app-root/src${DOCUMENTROOT}"

View file

@ -0,0 +1,12 @@
<IfModule mpm_prefork_module>
# This value should mirror what is set in MinSpareServers.
StartServers ${HTTPD_START_SERVERS}
MinSpareServers ${HTTPD_START_SERVERS}
MaxSpareServers ${HTTPD_MAX_SPARE_SERVERS}
# The MaxRequestWorkers directive sets the limit on the number of simultaneous requests that will be served.
# The default value, when no Cgroup limits are set is 256.
MaxRequestWorkers ${HTTPD_MAX_REQUEST_WORKERS}
ServerLimit ${HTTPD_MAX_REQUEST_WORKERS}
MaxRequestsPerChild 4000
MaxKeepAliveRequests 100
</IfModule>

View file

@ -0,0 +1,6 @@
# additional arbitrary httpd configuration provided by user using s2i
log_info 'Processing additional arbitrary httpd configuration provided by s2i ...'
process_extending_config_files ${APP_DATA}/httpd-cfg/ ${PHP_CONTAINER_SCRIPTS_PATH}/httpd-cnf/

View file

@ -0,0 +1,4 @@
source ${PHP_CONTAINER_SCRIPTS_PATH}/common.sh
# Copy SSL files provided in application source
process_ssl_certs

View file

@ -0,0 +1,6 @@
# additional arbitrary httpd configuration provided by user using s2i
log_info 'Processing additional arbitrary httpd configuration provided by s2i ...'
process_extending_config_files ${APP_DATA}/httpd-cfg/ ${PHP_CONTAINER_SCRIPTS_PATH}/httpd-cnf/

View file

@ -0,0 +1,4 @@
source ${PHP_CONTAINER_SCRIPTS_PATH}/common.sh
# Copy SSL files provided in application source
process_ssl_certs ${APP_ROOT}/src

56
s2i/bin/assemble Executable file
View file

@ -0,0 +1,56 @@
#!/bin/bash
set -e
source ${PHP_CONTAINER_SCRIPTS_PATH}/common.sh
shopt -s dotglob
echo "---> Installing application source..."
mv /tmp/src/* ./
if [ -f composer.json ]; then
echo "Found 'composer.json', installing dependencies using composer.phar... "
# Install Composer
TEMPFILE=$(mktemp)
RETRIES=6
for ((i=0; i<$RETRIES; i++)); do
if [ -z "$COMPOSER_INSTALLER" ]; then
export COMPOSER_INSTALLER="https://getcomposer.org/installer"
fi
echo "Downloading $COMPOSER_INSTALLER, attempt $((i+1))/$RETRIES"
curl -o $TEMPFILE $COMPOSER_INSTALLER && break
sleep 10
done
if [[ $i == $RETRIES ]]; then
echo "Download failed, giving up."
exit 1
fi
php <$TEMPFILE
if [ "$(ls -a /tmp/artifacts/ 2>/dev/null)" ]; then
echo "Restoring build artifacts"
mv /tmp/artifacts/* $HOME/
fi
# Change the repo mirror if provided
if [ -n "$COMPOSER_MIRROR" ]; then
./composer.phar config -g repositories.packagist composer $COMPOSER_MIRROR
fi
# Install App dependencies using Composer
./composer.phar install --no-interaction --no-ansi --optimize-autoloader $COMPOSER_ARGS
if [ ! -f composer.lock ]; then
echo -e "\nConsider adding a 'composer.lock' file into your source repository.\n"
fi
fi
# post-assemble files
process_extending_files ./php-post-assemble/ ${PHP_CONTAINER_SCRIPTS_PATH}/post-assemble/
# Fix source directory permissions
fix-permissions ./
fix-permissions ${HTTPD_CONFIGURATION_PATH}

57
s2i/bin/run Executable file
View file

@ -0,0 +1,57 @@
#!/bin/bash
source ${PHP_CONTAINER_SCRIPTS_PATH}/common.sh
export_vars=$(cgroup-limits); export $export_vars
export DOCUMENTROOT=${DOCUMENTROOT:-/}
# Default php.ini configuration values, all taken
# from php defaults.
export ERROR_REPORTING=${ERROR_REPORTING:-E_ALL & ~E_NOTICE}
export DISPLAY_ERRORS=${DISPLAY_ERRORS:-ON}
export DISPLAY_STARTUP_ERRORS=${DISPLAY_STARTUP_ERRORS:-OFF}
export TRACK_ERRORS=${TRACK_ERRORS:-OFF}
export HTML_ERRORS=${HTML_ERRORS:-ON}
export INCLUDE_PATH=${INCLUDE_PATH:-.:/opt/app-root/src:${PHP_DEFAULT_INCLUDE_PATH}}
export PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT:-128M}
export SESSION_NAME=${SESSION_NAME:-PHPSESSID}
export SESSION_HANDLER=${SESSION_HANDLER:-files}
export SESSION_PATH=${SESSION_PATH:-/tmp/sessions}
export SESSION_COOKIE_DOMAIN=${SESSION_COOKIE_DOMAIN:-}
export SESSION_COOKIE_HTTPONLY=${SESSION_COOKIE_HTTPONLY:-}
export SESSION_COOKIE_SECURE=${SESSION_COOKIE_SECURE:-0}
export SHORT_OPEN_TAG=${SHORT_OPEN_TAG:-OFF}
# TODO should be dynamically calculated based on container memory limit/16
export OPCACHE_MEMORY_CONSUMPTION=${OPCACHE_MEMORY_CONSUMPTION:-128}
export OPCACHE_REVALIDATE_FREQ=${OPCACHE_REVALIDATE_FREQ:-2}
export PHPRC=${PHPRC:-${PHP_SYSCONF_PATH}/php.ini}
export PHP_INI_SCAN_DIR=${PHP_INI_SCAN_DIR:-${PHP_SYSCONF_PATH}/php.d}
envsubst < /opt/app-root/etc/php.ini.template > ${PHP_SYSCONF_PATH}/php.ini
envsubst < /opt/app-root/etc/php.d/10-opcache.ini.template > ${PHP_SYSCONF_PATH}/php.d/10-opcache.ini
export HTTPD_START_SERVERS=${HTTPD_START_SERVERS:-8}
export HTTPD_MAX_SPARE_SERVERS=$((HTTPD_START_SERVERS+10))
if [ -n "${NO_MEMORY_LIMIT:-}" -o -z "${MEMORY_LIMIT_IN_BYTES:-}" ]; then
#
export HTTPD_MAX_REQUEST_WORKERS=${HTTPD_MAX_REQUEST_WORKERS:-256}
else
# A simple calculation for MaxRequestWorkers would be: Total Memory / Size Per Apache process.
# The total memory is determined from the Cgroups and the average size for the
# Apache process is estimated to 15MB.
max_clients_computed=$((MEMORY_LIMIT_IN_BYTES/1024/1024/15))
# The MaxClients should never be lower than StartServers, which is set to 5.
# In case the container has memory limit set to <64M we pin the MaxClients to 4.
[[ $max_clients_computed -le 4 ]] && max_clients_computed=4
export HTTPD_MAX_REQUEST_WORKERS=${HTTPD_MAX_REQUEST_WORKERS:-$max_clients_computed}
echo "-> Cgroups memory limit is set, using HTTPD_MAX_REQUEST_WORKERS=${HTTPD_MAX_REQUEST_WORKERS}"
fi
# pre-start files
process_extending_files ${APP_DATA}/php-pre-start/ ${PHP_CONTAINER_SCRIPTS_PATH}/pre-start/
exec httpd -D FOREGROUND

4
s2i/bin/save-artifacts Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
pushd ${HOME} >/dev/null
tar cf - vendor
popd >/dev/null

17
s2i/bin/usage Executable file
View file

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

243
test/run Executable file
View file

@ -0,0 +1,243 @@
#!/bin/bash
#
# The 'run' performs a simple test that verifies that S2I image.
# The main focus here is to excersise the S2I scripts.
#
# IMAGE_NAME specifies a name of the candidate image used for testing.
# The image has to be available before this script is executed.
#
IMAGE_NAME=${IMAGE_NAME-centos/php-71-centos-candidate}
# TODO: Make command compatible for Mac users
test_dir="$(readlink -zf $(dirname "${BASH_SOURCE[0]}"))"
image_dir=$(readlink -zf ${test_dir}/..)
source "${test_dir}/test-lib.sh"
# TODO: This should be part of the image metadata
test_port=8080
test_port_ssl=8443
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}/test-app ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args}
}
prepare() {
if ! image_exists ${IMAGE_NAME}; then
echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed."
exit 1
fi
# TODO: S2I build require the application is a valid 'GIT' repository, we
# should remove this restriction in the future when a file:// is used.
info "Build the test application image"
pushd ${test_dir}/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() {
run_args=${CONTAINER_ARGS:-}
docker run --user=100001 ${run_args} --cidfile=${cid_file} ${IMAGE_NAME}-testapp
}
cleanup_test_app() {
info "Cleaning up the test application"
if [ -f $cid_file ]; then
if container_exists; then
docker stop $(cat $cid_file)
docker rm $(cat $cid_file)
fi
rm $cid_file
fi
}
cleanup() {
info "Cleaning up the test application image"
if image_exists ${IMAGE_NAME}-testapp; then
docker rmi -f ${IMAGE_NAME}-testapp
fi
rm -rf ${test_dir}/test-app/.git
}
check_result() {
local result="$1"
if [[ "$result" != "0" ]]; then
info "TEST FAILED (${result})"
cleanup
exit $result
fi
}
wait_for_cid() {
local max_attempts=10
local sleep_time=1
local attempt=1
local result=1
info "Waiting for application container to start"
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'"
ct_s2i_usage ${IMAGE_NAME} ${s2i_args} &>/dev/null
}
test_docker_run_usage() {
info "Testing 'docker run' usage"
docker run ${IMAGE_NAME} &>/dev/null
}
test_scl_usage() {
local run_cmd="$1"
local expected="$2"
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
}
test_session() {
local check_port=$1 ; shift
local check_protocol=${1:-http}
cat $cid_file
info "Testing PHP session"
response=$(curl -s -k ${check_protocol}://$(container_ip):${check_port}/session_test.php)
if [ "${response}" != "Passed" ]; then
echo "ERROR starting PHP session. Test app returned: '${response}'"
return 1
fi
}
test_connection() {
local check_port=$1 ; shift
local check_protocol=${1:-http}
cat $cid_file
info "Testing the HTTP connection (${check_protocol}://$(container_ip):${check_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 -k ${check_protocol}://$(container_ip):${check_port}/)
status=$?
if [ $status -eq 0 ]; then
if [ $response_code -eq 200 ]; then
result=0
break
fi
fi
attempt=$(( $attempt + 1 ))
sleep $sleep_time
done
return $result
}
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
test_scl_usage "php --version" "7.1"
check_result $?
test_session ${test_port}
check_result $?
test_connection ${test_port}
check_result $?
test_connection ${test_port_ssl} https
check_result $?
cleanup_test_app
}
test_ssl() {
local cert_dir=/tmp
local cert_base=mycert
ct_gen_self_signed_cert_pem ${cert_dir} ${cert_base}
local private_key=${cert_dir}/${cert_base}-cert-selfsigned.pem
local cert_file=${cert_dir}/${cert_base}-key.pem
}
test_ssl_own_cert() {
ct_s2i_build_as_df file://${test_dir}/self-signed-ssl ${IMAGE_NAME} ${IMAGE_NAME}-test-self-signed-ssl ${s2i_args}
docker run -d --user=100001 ${run_args} --cidfile=${cid_file} ${IMAGE_NAME}-test-self-signed-ssl
test_connection ${test_port_ssl} https
check_result $?
echo | openssl s_client -showcerts -servername $(container_ip) -connect $(container_ip):${test_port_ssl} 2>/dev/null | openssl x509 -inform pem -noout -text >./servercert
openssl x509 -in ${test_dir}/self-signed-ssl/httpd-ssl/certs/server-cert-selfsigned.pem -inform pem -noout -text >./configcert
diff ./configcert ./servercert >cert.diff
}
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"
prepare
run_s2i_build
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 $?
# Test application with default uid
test_application
# Test application with random uid
CONTAINER_ARGS="--user 12345" test_application
echo "Testing npm availibility"
ct_npm_works
check_result $?
cleanup
test_ssl_own_cert
check_result $?
cleanup
info "All tests finished successfully."

77
test/run-openshift Executable file
View file

@ -0,0 +1,77 @@
#!/bin/bash
#
# Test the PHP 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
# change the branch to a different value if a new change in the example
# app needs to be tested
BRANCH_TO_TEST=master
set -eo nounset
test -n "${IMAGE_NAME-}" || false 'make sure $IMAGE_NAME is defined'
test -n "${VERSION-}" || false 'make sure $VERSION is defined'
test -n "${OS-}" || false 'make sure $OS is defined'
istag="php:$VERSION"
function test_file_upload() {
local image_name=$1
local service_name=${image_name##*/}
local app="https://github.com/openshift-qe/openshift-php-upload-demo"
local ip=""
echo "Running file upload test for: $image_name"
ct_os_new_project
ct_os_upload_image "$image_name" "$istag"
ct_os_deploy_s2i_image "$istag" "$app" --name "${service_name}"
ct_os_wait_pod_ready "$service_name" 60
# Wait until the app is prepared to receive files
ip=$(ct_os_get_service_ip "$service_name")
curl "$ip:8080" 2>/dev/null | grep -q "OpenShift File Upload Demonstration"
# Upload a file into the pod using the php app
curl -F fto=@README.md "$ip:8080/upload.php" &>/dev/null
ct_os_run_in_pod "$(ct_os_get_pod_name "$service_name")" ls uploaded/README.md >/dev/null
ct_os_delete_project
}
ct_os_cluster_up
# test local app
ct_os_test_s2i_app ${IMAGE_NAME} "https://github.com/sclorg/s2i-php-container.git" ${VERSION}/test/test-app "Test PHP passed"
ct_os_test_s2i_app ${IMAGE_NAME} "https://github.com/sclorg/cakephp-ex.git#${BRANCH_TO_TEST}" . 'Welcome to your CakePHP application on OpenShift'
# cakephp template does not work with version 5.6
if [[ "${VERSION}" > "5.6" ]] ; then
ct_os_test_template_app ${IMAGE_NAME} \
https://raw.githubusercontent.com/sclorg/cakephp-ex/${BRANCH_TO_TEST}/openshift/templates/cakephp.json \
php \
'Welcome to your CakePHP application on OpenShift' \
8080 http 200 "-p SOURCE_REPOSITORY_REF=${BRANCH_TO_TEST} -p SOURCE_REPOSITORY_URL=https://github.com/sclorg/cakephp-ex.git -p PHP_VERSION=${VERSION} -p NAME=php-testing"
fi
# test image update with s2i
case "$OS" in
rhel7) old_image=rhscl/php-${VERSION/./}-rhel7 ;;
# Fedora image isn't in registry yet
# fedora) old_image=${IMAGE_NAME/localhost\//}
*) old_image=centos/php-${VERSION/./}-centos7 ;;
esac
ct_os_test_image_update "$IMAGE_NAME" "${old_image}" "$istag" \
'ct_test_response "http://<IP>:8080" "200" "Test PHP passed"' \
"$istag~https://github.com/sclorg/s2i-php-container.git" \
--context-dir="$VERSION/test/test-app"
test_file_upload "$IMAGE_NAME"

View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIJAI4x7HuBG49oMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV
BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
Q29tcGFueSBMdGQwHhcNMTcxMjAzMjMzMzU3WhcNMTgwMTAyMjMzMzU3WjBCMQsw
CQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZh
dWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
vH4Vdq0a3UWUQd8Z6s2csxhxjAOyUx0rszGL0m3uTjQido6JRBdjN2dXiZc3LFoq
YeOKR3CeHsn7UdrlzaboHFDfjAaextse0740mB1g14H1bAS0POuTPeKa+3wGfzCb
sTSXnfSrICl3n2D/3KSO93WwmS90kBD6HmKt5nfkLpJnROM/4bHmuoV0Ry8CDjzj
mka7pQU4yzyMKLU3sHpncZU6g7o4Vezic9ksVzIAbdPCSbF7ktVz/hisyCuzyKN6
s2327jq593vBgGOsNU5PDPDjKW74Q0Bv/FxPK4nx+o4IkcRW1QEb+yAx8XOM7CDZ
ViKvI/A0b+Y4Y3rIQ465+wIDAQABo1MwUTAdBgNVHQ4EFgQUAY1i6ZNbqO1+46aw
pldCyPaWoYswHwYDVR0jBBgwFoAUAY1i6ZNbqO1+46awpldCyPaWoYswDwYDVR0T
AQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEADhGjnYGq9JvQcygMYEQiIdyS
t06Nu7NUkWz52GJp7WFognWyG+0jAomBR0GSUchfubvVZ7cHIaVKLhiGOqg+HIol
7tNRfvE6x/Idk674g6OTRAWxO/wOlgnRMpRy6XhHOtb4HcPcpWFZJS8MC8+HRWIs
kzMErXe0/obnKn9O04kcEREfmB7kfcD4ooqk5gwbdQk1W6a44LcN6AB5qYPjOzgF
Qnb2aLQW9XhgNhiMsYqDzCZsy0az0rz7NgkVOnKrGJ8x3kVX13GR2joVVHOazms9
Gd90z+mLMDTbqCRGIPMLvEp4HtAmBxbgsj/zHyinajIqV96B3Cr3zTdW29lHJg==
-----END CERTIFICATE-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC8fhV2rRrdRZRB
3xnqzZyzGHGMA7JTHSuzMYvSbe5ONCJ2jolEF2M3Z1eJlzcsWiph44pHcJ4eyftR
2uXNpugcUN+MBp7G2x7TvjSYHWDXgfVsBLQ865M94pr7fAZ/MJuxNJed9KsgKXef
YP/cpI73dbCZL3SQEPoeYq3md+QukmdE4z/hsea6hXRHLwIOPOOaRrulBTjLPIwo
tTewemdxlTqDujhV7OJz2SxXMgBt08JJsXuS1XP+GKzIK7PIo3qzbfbuOrn3e8GA
Y6w1Tk8M8OMpbvhDQG/8XE8rifH6jgiRxFbVARv7IDHxc4zsINlWIq8j8DRv5jhj
eshDjrn7AgMBAAECggEARZxeutxE/pCypv0IqkFS7IVLccTvt2gfemcC1yzIBFOW
oqgTI3Vrq8tbdbHFq3iFDG+m4qlBi+dWDC3GDoPkVoi7dg//1TqZEOO+sqqu2Afj
pge6tIDfeMxWJifwkkpWRURB9hCknhUSW2bMNyUCs3rgREJVTtsmM9CHnoSKXXQL
aOeYXalFVpx3ceK+xdp0VGfpsqEabBKs0yy3EDiQy2huoWce3EVFLVrwx/IkhcsZ
JlI5LPpoiTglSs1g9i88JHS2slBtKtb1lWl/yXHhK1g7s34c6f9jP8snuFE5ddMn
0L4GDA9teaPGvB533eb2RIFy2kUYgpr5c03G6rpoOQKBgQDpY6BFJkPGENnC5Bdb
fJCuN2nyRdC1qvv6ESFaQYb0s6QjKDqpb0dUSYN3+zNgtiAysbQLeU/d9mmt4UR8
ohjRkOySU0eQ/YNFokjw6g6GPoiMHJJ9cP75NA94uIMIUTY7uHEWWZwXI5UphdPC
p5/3MaF1VlYQys9a5wtiEaDSfQKBgQDOwPV0zQjUabkVQ4yV0amP8xybvHH8ghG0
RMStHg96RfDmg35JQaw22A2xiVROCoZgLqiE1DFSl/3gBF/vfqBh/uzdxwNerJC6
ROdCxyS4rys5d/02P4aNOa73sD+ZKyEZRTF1v3bmOGKidRFF5oxIpuHjFWlJFKx1
O/b3AI0v1wKBgQC/L4N84emm+OrKAfs4UIRckrxRYOulxhmAMkQ2IXOiRP5yZmQX
pDa0TzxJLxhZYxhhLr0koQ3R8CeF7wEhb9AQ7D0/aMU5etLsWhKSd8nKIrPMwyMl
a0kTb5g09kEwsQZSSbcp7eI1+koYp65eyN37q0ZuTnlWbC0MdDQY9APgKQKBgQCb
HqaKNXLUe2XDkGSf2ygOumXSanZS7vt9dsLg59bQ9DyjljBfogglNcBAXTqFOtxK
uXbyAYnn3+U399BKjYSjQXJRioj6tRn4xs2DiooAjlwtx9qQouS+fHLLns54iqVQ
oltTbo00eUV3gcGt4iWKNLrxdxUBIaOqaY0HEMDdDQKBgQCRvcHDF7JSPuBiO3Tw
PSOUD4q6dD/dhI+X2ZKg83w94SZXXms6eMSbedUkLoJ8TDunmdRUUWb6rgP/pJwr
zKRTskItF15i9IWCwC6jBrSfx5n2JcSoBALyc0aR9heF0GQjWwqURd+PC/msomrW
z9SCl8mpQVFtBlui7PcnDLTFAg==
-----END PRIVATE KEY-----

View file

@ -0,0 +1 @@
SSL test works

1
test/test-app Submodule

@ -0,0 +1 @@
Subproject commit df325a98b3498cbb0f18009f4640c4d6205934fe

925
test/test-lib-openshift.sh Normal file
View file

@ -0,0 +1,925 @@
# Set of functions for testing docker images in OpenShift using 'oc' command
# ct_os_get_status
# --------------------
# Returns status of all objects to make debugging easier.
function ct_os_get_status() {
oc get all
oc status
}
# ct_os_print_logs
# --------------------
# Returns status of all objects and logs from all pods.
function ct_os_print_logs() {
ct_os_get_status
while read pod_name; do
echo "INFO: printing logs for pod ${pod_name}"
oc logs ${pod_name}
done < <(oc get pods --no-headers=true -o custom-columns=NAME:.metadata.name)
}
# ct_os_enable_print_logs
# --------------------
# Enables automatic printing of pod logs on ERR.
function ct_os_enable_print_logs() {
set -E
trap ct_os_print_logs ERR
}
# ct_get_public_ip
# --------------------
# Returns best guess for the IP that the node is accessible from other computers.
# This is a bit funny heuristic, simply goes through all IPv4 addresses that
# hostname -I returns and de-prioritizes IP addresses commonly used for local
# addressing. The rest of addresses are taken as public with higher probability.
function ct_get_public_ip() {
local hostnames=$(hostname -I)
local public_ip=''
local found_ip
for guess_exp in '127\.0\.0\.1' '192\.168\.[0-9\.]*' '172\.[0-9\.]*' \
'10\.[0-9\.]*' '[0-9\.]*' ; do
found_ip=$(echo "${hostnames}" | grep -oe "${guess_exp}")
if [ -n "${found_ip}" ] ; then
hostnames=$(echo "${hostnames}" | sed -e "s/${found_ip}//")
public_ip="${found_ip}"
fi
done
if [ -z "${public_ip}" ] ; then
echo "ERROR: public IP could not be guessed." >&2
return 1
fi
echo "${public_ip}"
}
# ct_os_run_in_pod POD_NAME CMD
# --------------------
# Runs [cmd] in the pod specified by prefix [pod_prefix].
# Arguments: pod_name - full name of the pod
# Arguments: cmd - command to be run in the pod
function ct_os_run_in_pod() {
local pod_name="$1" ; shift
oc exec "$pod_name" -- "$@"
}
# ct_os_get_service_ip SERVICE_NAME
# --------------------
# Returns IP of the service specified by [service_name].
# Arguments: service_name - name of the service
function ct_os_get_service_ip() {
local service_name="${1}" ; shift
oc get "svc/${service_name}" -o yaml | grep clusterIP | \
cut -d':' -f2 | grep -oe '172\.30\.[0-9\.]*'
}
# ct_os_get_all_pods_status
# --------------------
# Returns status of all pods.
function ct_os_get_all_pods_status() {
oc get pods -o custom-columns=Ready:status.containerStatuses[0].ready,NAME:.metadata.name
}
# ct_os_get_all_pods_name
# --------------------
# Returns the full name of all pods.
function ct_os_get_all_pods_name() {
oc get pods --no-headers -o custom-columns=NAME:.metadata.name
}
# ct_os_get_pod_status POD_PREFIX
# --------------------
# Returns status of the pod specified by prefix [pod_prefix].
# Note: Ignores -build and -deploy pods
# Arguments: pod_prefix - prefix or whole ID of the pod
function ct_os_get_pod_status() {
local pod_prefix="${1}" ; shift
ct_os_get_all_pods_status | grep -e "${pod_prefix}" | grep -Ev "(build|deploy)$" \
| awk '{print $1}' | head -n 1
}
# ct_os_get_pod_name POD_PREFIX
# --------------------
# Returns the full name of pods specified by prefix [pod_prefix].
# Note: Ignores -build and -deploy pods
# Arguments: pod_prefix - prefix or whole ID of the pod
function ct_os_get_pod_name() {
local pod_prefix="${1}" ; shift
ct_os_get_all_pods_name | grep -e "^${pod_prefix}" | grep -Ev "(build|deploy)$"
}
# ct_os_get_pod_ip POD_NAME
# --------------------
# Returns the ip of the pod specified by [pod_name].
# Arguments: pod_name - full name of the pod
function ct_os_get_pod_ip() {
local pod_name="${1}"
oc get pod "$pod_name" --no-headers -o custom-columns=IP:status.podIP
}
# ct_os_check_pod_readiness POD_PREFIX STATUS
# --------------------
# Checks whether the pod is ready.
# Arguments: pod_prefix - prefix or whole ID of the pod
# Arguments: status - expected status (true, false)
function ct_os_check_pod_readiness() {
local pod_prefix="${1}" ; shift
local status="${1}" ; shift
test "$(ct_os_get_pod_status ${pod_prefix})" == "${status}"
}
# ct_os_wait_pod_ready POD_PREFIX TIMEOUT
# --------------------
# Wait maximum [timeout] for the pod becomming ready.
# Arguments: pod_prefix - prefix or whole ID of the pod
# Arguments: timeout - how many seconds to wait seconds
function ct_os_wait_pod_ready() {
local pod_prefix="${1}" ; shift
local timeout="${1}" ; shift
SECONDS=0
echo -n "Waiting for ${pod_prefix} pod becoming ready ..."
while ! ct_os_check_pod_readiness "${pod_prefix}" "true" ; do
echo -n "."
[ ${SECONDS} -gt ${timeout} ] && echo " FAIL" && return 1
sleep 3
done
echo " DONE"
}
# ct_os_wait_rc_ready POD_PREFIX TIMEOUT
# --------------------
# Wait maximum [timeout] for the rc having desired number of replicas ready.
# Arguments: pod_prefix - prefix of the replication controller
# Arguments: timeout - how many seconds to wait seconds
function ct_os_wait_rc_ready() {
local pod_prefix="${1}" ; shift
local timeout="${1}" ; shift
SECONDS=0
echo -n "Waiting for ${pod_prefix} pod becoming ready ..."
while ! test "$((oc get --no-headers statefulsets; oc get --no-headers rc) 2>/dev/null \
| grep "^${pod_prefix}" | awk '$2==$3 {print "ready"}')" == "ready" ; do
echo -n "."
[ ${SECONDS} -gt ${timeout} ] && echo " FAIL" && return 1
sleep 3
done
echo " DONE"
}
# ct_os_deploy_pure_image IMAGE [ENV_PARAMS, ...]
# --------------------
# Runs [image] in the openshift and optionally specifies env_params
# as environment variables to the image.
# Arguments: image - prefix or whole ID of the pod to run the cmd in
# Arguments: env_params - environment variables parameters for the images.
function ct_os_deploy_pure_image() {
local image="${1}" ; shift
# ignore error exit code, because oc new-app returns error when image exists
oc new-app ${image} "$@" || :
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
# ct_os_deploy_s2i_image IMAGE APP [ENV_PARAMS, ... ]
# --------------------
# Runs [image] and [app] in the openshift and optionally specifies env_params
# as environment variables to the image.
# Arguments: image - prefix or whole ID of the pod to run the cmd in
# Arguments: app - url or local path to git repo with the application sources.
# Arguments: env_params - environment variables parameters for the images.
function ct_os_deploy_s2i_image() {
local image="${1}" ; shift
local app="${1}" ; shift
# ignore error exit code, because oc new-app returns error when image exists
oc new-app "${image}~${app}" "$@" || :
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
# ct_os_deploy_template_image TEMPLATE [ENV_PARAMS, ...]
# --------------------
# Runs template in the openshift and optionally gives env_params to use
# specific values in the template.
# Arguments: template - prefix or whole ID of the pod to run the cmd in
# Arguments: env_params - environment variables parameters for the template.
# Example usage: ct_os_deploy_template_image mariadb-ephemeral-template.yaml \
# DATABASE_SERVICE_NAME=mysql-57-centos7 \
# DATABASE_IMAGE=mysql-57-centos7 \
# MYSQL_USER=testu \
# MYSQL_PASSWORD=testp \
# MYSQL_DATABASE=testdb
function ct_os_deploy_template_image() {
local template="${1}" ; shift
oc process -f "${template}" "$@" | oc create -f -
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
# _ct_os_get_uniq_project_name
# --------------------
# Returns a uniq name of the OpenShift project.
function _ct_os_get_uniq_project_name() {
local r
while true ; do
r=${RANDOM}
mkdir /var/tmp/sclorg-test-${r} &>/dev/null && echo sclorg-test-${r} && break
done
}
# ct_os_new_project [PROJECT]
# --------------------
# Creates a new project in the openshfit using 'os' command.
# Arguments: project - project name, uses a new random name if omitted
# Expects 'os' command that is properly logged in to the OpenShift cluster.
# Not using mktemp, because we cannot use uppercase characters.
function ct_os_new_project() {
if [ "${CT_SKIP_NEW_PROJECT:-false}" == 'true' ] ; then
echo "Creating project skipped."
return
fi
local project_name="${1:-$(_ct_os_get_uniq_project_name)}" ; shift || :
oc new-project ${project_name}
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
# ct_os_delete_project [PROJECT]
# --------------------
# Deletes the specified project in the openshfit
# Arguments: project - project name, uses the current project if omitted
function ct_os_delete_project() {
if [ "${CT_SKIP_NEW_PROJECT:-false}" == 'true' ] ; then
echo "Deleting project skipped, cleaning objects only."
ct_delete_all_objects
return
fi
local project_name="${1:-$(oc project -q)}" ; shift || :
oc delete project "${project_name}"
}
# ct_delete_all_objects
# -----------------
# Deletes all objects within the project.
# Handy when we have one project and want to run more tests.
function ct_delete_all_objects() {
for x in bc builds dc is isimage istag po pv pvc rc routes secrets svc ; do
oc delete $x --all
done
# for some objects it takes longer to be really deleted, so a dummy sleep
# to avoid some races when other test can see not-yet-deleted objects and can fail
sleep 10
}
# ct_os_docker_login
# --------------------
# Logs in into docker daemon
# Uses global REGISRTY_ADDRESS environment variable for arbitrary registry address.
# Does not do anything if REGISTRY_ADDRESS is set.
function ct_os_docker_login() {
[ -n "${REGISTRY_ADDRESS:-}" ] && "REGISTRY_ADDRESS set, not trying to docker login." && return 0
# docker login fails with "404 page not found" error sometimes, just try it more times
for i in `seq 12` ; do
docker login -u developer -p $(oc whoami -t) ${REGISRTY_ADDRESS:-172.30.1.1:5000} && return 0 || :
sleep 5
done
return 1
}
# ct_os_upload_image IMAGE [IMAGESTREAM]
# --------------------
# Uploads image from local registry to the OpenShift internal registry.
# Arguments: image - image name to upload
# Arguments: imagestream - name and tag to use for the internal registry.
# In the format of name:tag ($image_name:latest by default)
# Uses global REGISRTY_ADDRESS environment variable for arbitrary registry address.
function ct_os_upload_image() {
local input_name="${1}" ; shift
local image_name=${input_name##*/}
local imagestream=${1:-$image_name:latest}
local output_name="${REGISRTY_ADDRESS:-172.30.1.1:5000}/$(oc project -q)/$imagestream"
ct_os_docker_login
docker tag ${input_name} ${output_name}
docker push ${output_name}
}
# ct_os_install_in_centos
# --------------------
# Installs os cluster in CentOS
function ct_os_install_in_centos() {
yum install -y centos-release-openshift-origin
yum install -y wget git net-tools bind-utils iptables-services bridge-utils\
bash-completion origin-clients docker origin-clients
}
# ct_os_cluster_up [DIR, IS_PUBLIC, CLUSTER_VERSION]
# --------------------
# Runs the local OpenShift cluster using 'oc cluster up' and logs in as developer.
# Arguments: dir - directory to keep configuration data in, random if omitted
# Arguments: is_public - sets either private or public hostname for web-UI,
# use "true" for allow remote access to the web-UI,
# "false" is default
# Arguments: cluster_version - version of the OpenShift cluster to use, empty
# means default version of `oc`; example value: 3.7;
# also can be specified outside by OC_CLUSTER_VERSION
function ct_os_cluster_up() {
ct_os_cluster_running && echo "Cluster already running. Nothing is done." && return 0
ct_os_logged_in && echo "Already logged in to a cluster. Nothing is done." && return 0
mkdir -p /var/tmp/openshift
local dir="${1:-$(mktemp -d /var/tmp/openshift/os-data-XXXXXX)}" ; shift || :
local is_public="${1:-'false'}" ; shift || :
local default_cluster_version=${OC_CLUSTER_VERSION:-}
local cluster_version=${1:-${default_cluster_version}} ; shift || :
if ! grep -qe '--insecure-registry.*172\.30\.0\.0' /etc/sysconfig/docker ; then
sed -i "s|OPTIONS='|OPTIONS='--insecure-registry 172.30.0.0/16 |" /etc/sysconfig/docker
fi
systemctl stop firewalld || :
setenforce 0
iptables -F
systemctl restart docker
local cluster_ip="127.0.0.1"
[ "${is_public}" == "true" ] && cluster_ip=$(ct_get_public_ip)
if [ -n "${cluster_version}" ] ; then
# if $cluster_version is not set, we simply use oc that is available
ct_os_set_path_oc "${cluster_version}"
fi
mkdir -p ${dir}/{config,data,pv}
case $(oc version| head -n 1) in
"oc v3.1"?.*)
oc cluster up --base-dir="${dir}/data" --public-hostname="${cluster_ip}"
;;
"oc v3."*)
oc cluster up --host-data-dir="${dir}/data" --host-config-dir="${dir}/config" \
--host-pv-dir="${dir}/pv" --use-existing-config --public-hostname="${cluster_ip}"
;;
*)
echo "ERROR: Unexpected oc version." >&2
return 1
;;
esac
oc version
oc login -u system:admin
oc project default
ct_os_wait_rc_ready docker-registry 180
ct_os_wait_rc_ready router 30
oc login -u developer -p developer
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
# ct_os_cluster_down
# --------------------
# Shuts down the local OpenShift cluster using 'oc cluster down'
function ct_os_cluster_down() {
oc cluster down
}
# ct_os_cluster_running
# --------------------
# Returns 0 if oc cluster is running
function ct_os_cluster_running() {
oc cluster status &>/dev/null
}
# ct_os_logged_in
# ---------------
# Returns 0 if logged in to a cluster (remote or local)
function ct_os_logged_in() {
oc whoami >/dev/null
}
# ct_os_set_path_oc OC_VERSION
# --------------------
# This is a trick that helps using correct version of the `oc`:
# The input is version of the openshift in format v3.6.0 etc.
# If the currently available version of oc is not of this version,
# it first takes a look into /usr/local/oc-<ver>/bin directory,
# and if not found there it downloads the community release from github.
# In the end the PATH variable is changed, so the other tests can still use just 'oc'.
# Arguments: oc_version - X.Y part of the version of OSE (e.g. 3.9)
function ct_os_set_path_oc() {
local oc_version=$(ct_os_get_latest_ver $1)
local oc_path
if oc version | grep -q "oc ${oc_version%.*}." ; then
echo "Binary oc found already available in version ${oc_version}: `which oc` Doing noting."
return 0
fi
# first check whether we already have oc available in /usr/local
local installed_oc_path="/usr/local/oc-${oc_version%.*}/bin"
if [ -x "${installed_oc_path}/oc" ] ; then
oc_path="${installed_oc_path}"
echo "Binary oc found in ${installed_oc_path}" >&2
else
# oc not available in /usr/local, try to download it from github (community release)
oc_path="/tmp/oc-${oc_version}-bin"
ct_os_download_upstream_oc "${oc_version}" "${oc_path}"
fi
if [ -z "${oc_path}/oc" ] ; then
echo "ERROR: oc not found installed, nor downloaded" >&1
return 1
fi
export PATH="${oc_path}:${PATH}"
if ! oc version | grep -q "oc ${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}: `which oc`"
fi
}
# ct_os_get_latest_ver VERSION_PART_X
# --------------------
# Returns full version (vX.Y.Z) from part of the version (X.Y)
# Arguments: vxy - X.Y part of the version
# Returns vX.Y.Z variant of the version
function ct_os_get_latest_ver(){
local vxy="v$1"
for vz in {3..0} ; do
curl -sif "https://github.com/openshift/origin/releases/tag/${vxy}.${vz}" >/dev/null && echo "${vxy}.${vz}" && return 0
done
echo "ERROR: version ${vxy} not found in https://github.com/openshift/origin/tags" >&2
return 1
}
# ct_os_download_upstream_oc OC_VERSION OUTPUT_DIR
# --------------------
# Downloads a particular version of openshift-origin-client-tools from
# github into specified output directory
# Arguments: oc_version - version of OSE (e.g. v3.7.2)
# Arguments: output_dir - output directory
function ct_os_download_upstream_oc() {
local oc_version=$1
local output_dir=$2
# check whether we already have the binary in place
[ -x "${output_dir}/oc" ] && return 0
mkdir -p "${output_dir}"
# using html output instead of https://api.github.com/repos/openshift/origin/releases/tags/${oc_version},
# because API is limited for number of queries if not authenticated
tarball=$(curl -si "https://github.com/openshift/origin/releases/tag/${oc_version}" | grep -o -e "openshift-origin-client-tools-${oc_version}-[a-f0-9]*-linux-64bit.tar.gz" | head -n 1)
# download, unpack the binaries and then put them into output directory
echo "Downloading https://github.com/openshift/origin/releases/download/${oc_version}/${tarball} into ${output_dir}/" >&2
curl -sL https://github.com/openshift/origin/releases/download/${oc_version}/"${tarball}" | tar -C "${output_dir}" -xz
mv -f "${output_dir}"/"${tarball%.tar.gz}"/* "${output_dir}/"
rmdir "${output_dir}"/"${tarball%.tar.gz}"
}
# ct_os_test_s2i_app_func IMAGE APP CONTEXT_DIR CHECK_CMD [OC_ARGS]
# --------------------
# Runs [image] and [app] in the openshift and optionally specifies env_params
# as environment variables to the image. Then check the container by arbitrary
# function given as argument (such an argument may include <IP> string,
# that will be replaced with actual IP).
# Arguments: image - prefix or whole ID of the pod to run the cmd in (compulsory)
# Arguments: app - url or local path to git repo with the application sources (compulsory)
# Arguments: context_dir - sub-directory inside the repository with the application sources (compulsory)
# Arguments: check_command - CMD line that checks whether the container works (compulsory; '<IP>' will be replaced with actual IP)
# Arguments: oc_args - all other arguments are used as additional parameters for the `oc new-app`
# command, typically environment variables (optional)
function ct_os_test_s2i_app_func() {
local image_name=${1}
local app=${2}
local context_dir=${3}
local check_command=${4}
local oc_args=${5:-}
local import_image=${6:-}
local image_name_no_namespace=${image_name##*/}
local service_name="${image_name_no_namespace}-testing"
local image_tagged="${image_name_no_namespace}:${VERSION}"
if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then
echo "ERROR: ct_os_test_s2i_app_func() requires at least 4 arguments that cannot be emtpy." >&2
return 1
fi
ct_os_new_project
# Create a specific imagestream tag for the image so that oc cannot use anything else
if [ "${CT_SKIP_UPLOAD_IMAGE:-false}" == 'true' ] ; then
if [ -n "${import_image}" ] ; then
echo "Importing image ${import_image} as ${image_name}:${VERSION}"
oc import-image ${image_name}:${VERSION} --from ${import_image} --confirm
else
echo "Uploading and importing image skipped."
fi
else
if [ -n "${import_image}" ] ; then
echo "Warning: Import image ${import_image} requested, but uploading image ${image_name} instead."
fi
ct_os_upload_image "${image_name}" "${image_tagged}"
fi
local app_param="${app}"
if [ -d "${app}" ] ; then
# for local directory, we need to copy the content, otherwise too smart os command
# pulls the git remote repository instead
app_param=$(ct_obtain_input "${app}")
fi
ct_os_deploy_s2i_image "${image_tagged}" "${app_param}" \
--context-dir="${context_dir}" \
--name "${service_name}" \
${oc_args}
if [ -d "${app}" ] ; then
# in order to avoid weird race seen sometimes, let's wait shortly
# before starting the build explicitly
sleep 5
oc start-build "${service_name}" --from-dir="${app_param}"
fi
ct_os_wait_pod_ready "${service_name}" 300
local ip=$(ct_os_get_service_ip "${service_name}")
local check_command_exp=$(echo "$check_command" | sed -e "s/<IP>/$ip/g")
echo " Checking APP using $check_command_exp ..."
local result=0
eval "$check_command_exp" || result=1
if [ $result -eq 0 ] ; then
echo " Check passed."
else
echo " Check failed."
fi
ct_os_delete_project
return $result
}
# ct_os_test_s2i_app IMAGE APP CONTEXT_DIR EXPECTED_OUTPUT [PORT, PROTOCOL, RESPONSE_CODE, OC_ARGS, ... ]
# --------------------
# Runs [image] and [app] in the openshift and optionally specifies env_params
# as environment variables to the image. Then check the http response.
# Arguments: image - prefix or whole ID of the pod to run the cmd in (compulsory)
# Arguments: app - url or local path to git repo with the application sources (compulsory)
# Arguments: context_dir - sub-directory inside the repository with the application sources (compulsory)
# Arguments: expected_output - PCRE regular expression that must match the response body (compulsory)
# Arguments: port - which port to use (optional; default: 8080)
# Arguments: protocol - which protocol to use (optional; default: http)
# Arguments: response_code - what http response code to expect (optional; default: 200)
# Arguments: oc_args - all other arguments are used as additional parameters for the `oc new-app`
# command, typically environment variables (optional)
function ct_os_test_s2i_app() {
local image_name=${1}
local app=${2}
local context_dir=${3}
local expected_output=${4}
local port=${5:-8080}
local protocol=${6:-http}
local response_code=${7:-200}
local oc_args=${8:-}
local import_image=${9:-}
if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then
echo "ERROR: ct_os_test_s2i_app() requires at least 4 arguments that cannot be emtpy." >&2
return 1
fi
ct_os_test_s2i_app_func "${image_name}" \
"${app}" \
"${context_dir}" \
"ct_os_test_response_internal '${protocol}://<IP>:${port}' '${response_code}' '${expected_output}'" \
"${oc_args}" "${import_image}"
}
# ct_os_test_template_app_func IMAGE APP IMAGE_IN_TEMPLATE CHECK_CMD [OC_ARGS]
# --------------------
# Runs [image] and [app] in the openshift and optionally specifies env_params
# as environment variables to the image. Then check the container by arbitrary
# function given as argument (such an argument may include <IP> string,
# that will be replaced with actual IP).
# Arguments: image_name - prefix or whole ID of the pod to run the cmd in (compulsory)
# Arguments: template - url or local path to a template to use (compulsory)
# Arguments: name_in_template - image name used in the template
# Arguments: check_command - CMD line that checks whether the container works (compulsory; '<IP>' will be replaced with actual IP)
# Arguments: oc_args - all other arguments are used as additional parameters for the `oc new-app`
# command, typically environment variables (optional)
# Arguments: other_images - some templates need other image to be pushed into the OpenShift registry,
# specify them in this parameter as "<image>|<tag>", where "<image>" is a full image name
# (including registry if needed) and "<tag>" is a tag under which the image should be available
# in the OpenShift registry.
function ct_os_test_template_app_func() {
local image_name=${1}
local template=${2}
local name_in_template=${3}
local check_command=${4}
local oc_args=${5:-}
local other_images=${6:-}
local import_image=${7:-}
if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then
echo "ERROR: ct_os_test_template_app_func() requires at least 4 arguments that cannot be emtpy." >&2
return 1
fi
local service_name="${name_in_template}-testing"
local image_tagged="${name_in_template}:${VERSION}"
ct_os_new_project
# Create a specific imagestream tag for the image so that oc cannot use anything else
if [ "${CT_SKIP_UPLOAD_IMAGE:-false}" == 'true' ] ; then
if [ -n "${import_image}" ] ; then
echo "Importing image ${import_image} as ${image_name}:${VERSION}"
oc import-image ${image_name}:${VERSION} --from ${import_image} --confirm
else
echo "Uploading and importing image skipped."
fi
else
if [ -n "${import_image}" ] ; then
echo "Warning: Import image ${import_image} requested, but uploading image ${image_name} instead."
fi
ct_os_upload_image "${image_name}" "${image_tagged}"
# upload also other images, that template might need (list of pairs in the format <image>|<tag>
local images_tags_a
local i_t
for i_t in ${other_images} ; do
echo "${i_t}"
IFS='|' read -ra image_tag_a <<< "${i_t}"
docker pull "${image_tag_a[0]}"
ct_os_upload_image "${image_tag_a[0]}" "${image_tag_a[1]}"
done
fi
local local_template=$(ct_obtain_input "${template}")
local namespace=${CT_NAMESPACE:-$(oc project -q)}
oc new-app ${local_template} \
--name "${name_in_template}" \
-p NAMESPACE="${namespace}" \
${oc_args}
ct_os_wait_pod_ready "${service_name}" 300
local ip=$(ct_os_get_service_ip "${service_name}")
local check_command_exp=$(echo "$check_command" | sed -e "s/<IP>/$ip/g")
echo " Checking APP using $check_command_exp ..."
local result=0
eval "$check_command_exp" || result=1
if [ $result -eq 0 ] ; then
echo " Check passed."
else
echo " Check failed."
fi
ct_os_delete_project
return $result
}
# params:
# ct_os_test_template_app IMAGE APP IMAGE_IN_TEMPLATE EXPECTED_OUTPUT [PORT, PROTOCOL, RESPONSE_CODE, OC_ARGS, ... ]
# --------------------
# Runs [image] and [app] in the openshift and optionally specifies env_params
# as environment variables to the image. Then check the http response.
# Arguments: image_name - prefix or whole ID of the pod to run the cmd in (compulsory)
# Arguments: template - url or local path to a template to use (compulsory)
# Arguments: name_in_template - image name used in the template
# Arguments: expected_output - PCRE regular expression that must match the response body (compulsory)
# Arguments: port - which port to use (optional; default: 8080)
# Arguments: protocol - which protocol to use (optional; default: http)
# Arguments: response_code - what http response code to expect (optional; default: 200)
# Arguments: oc_args - all other arguments are used as additional parameters for the `oc new-app`
# command, typically environment variables (optional)
# Arguments: other_images - some templates need other image to be pushed into the OpenShift registry,
# specify them in this parameter as "<image>|<tag>", where "<image>" is a full image name
# (including registry if needed) and "<tag>" is a tag under which the image should be available
# in the OpenShift registry.
function ct_os_test_template_app() {
local image_name=${1}
local template=${2}
local name_in_template=${3}
local expected_output=${4}
local port=${5:-8080}
local protocol=${6:-http}
local response_code=${7:-200}
local oc_args=${8:-}
local other_images=${9:-}
local import_image=${10:-}
if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then
echo "ERROR: ct_os_test_template_app() requires at least 4 arguments that cannot be emtpy." >&2
return 1
fi
ct_os_test_template_app_func "${image_name}" \
"${template}" \
"${name_in_template}" \
"ct_os_test_response_internal '${protocol}://<IP>:${port}' '${response_code}' '${expected_output}'" \
"${oc_args}" \
"${other_images}" \
"${import_image}"
}
# ct_os_test_image_update IMAGE_NAME OLD_IMAGE ISTAG CHECK_FUNCTION OC_ARGS
# --------------------
# Runs an image update test with [image] uploaded to [is] imagestream
# and checks the services using an arbitrary function provided in [check_function].
# Arguments: image_name - prefix or whole ID of the pod to run the cmd in (compulsory)
# Arguments: old_image - valid name of the image from the registry
# Arguments: istag - imagestream to upload the images into (compulsory)
# Arguments: check_function - command to be run to check functionality of created services (compulsory)
# Arguments: oc_args - arguments to use during oc new-app (compulsory)
ct_os_test_image_update() {
local image_name=$1; shift
local old_image=$1; shift
local istag=$1; shift
local check_function=$1; shift
local service_name=${image_name##*/}
local ip="" check_command_exp=""
echo "Running image update test for: $image_name"
ct_os_new_project
# Get current image from repository and create an imagestream
docker pull "$old_image:latest" 2>/dev/null
ct_os_upload_image "$old_image" "$istag"
# Setup example application with curent image
oc new-app "$@" --name "$service_name"
ct_os_wait_pod_ready "$service_name" 60
# Check application output
ip=$(ct_os_get_service_ip "$service_name")
check_command_exp=${check_function//<IP>/$ip}
ct_assert_cmd_success "$check_command_exp"
# Tag built image into the imagestream and wait for rebuild
ct_os_upload_image "$image_name" "$istag"
ct_os_wait_pod_ready "${service_name}-2" 60
# Check application output
ip=$(ct_os_get_service_ip "$service_name")
check_command_exp=${check_function//<IP>/$ip}
ct_assert_cmd_success "$check_command_exp"
ct_os_delete_project
}
# ct_os_deploy_cmd_image IMAGE_NAME
# --------------------
# Runs a special command pod, a pod that does nothing, but includes utilities for testing.
# A typical usage is a mysql pod that includes mysql commandline, that we need for testing.
# Running commands inside this command pod is done via ct_os_cmd_image_run function.
# The pod is not run again if already running.
# Arguments: image_name - image to be used as a command pod
function ct_os_deploy_cmd_image() {
local image_name=${1}
oc get pod command-app &>/dev/null && echo "command POD already running" && return 0
echo "command POD not running yet, will start one called command-app"
oc create -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: command-app
spec:
containers:
- name: command-container
image: "${image_name}"
command: ["sleep"]
args: ["3h"]
restartPolicy: OnFailure
EOF
SECONDS=0
echo -n "Waiting for command POD ."
while [ $SECONDS -lt 180 ] ; do
sout="$(ct_os_cmd_image_run 'echo $((11*11))')"
grep -q '^121$' <<< "$sout" && echo "DONE" && return 0 || :
sleep 3
echo -n "."
done
echo "FAIL"
return 1
}
# ct_os_cmd_image_run CMD [ ARG ... ]
# --------------------
# Runs a command CMD inside a special command pod
# Arguments: cmd - shell command with args to run in a pod
function ct_os_cmd_image_run() {
oc exec command-app -- bash -c "$@"
}
# ct_os_test_response_internal
# ----------------
# Perform GET request to the application container, checks output with
# a reg-exp and HTTP response code.
# That all is done inside an image in the cluster, so the function is used
# typically in clusters that are not accessible outside.
# The interanal image is a python image that should include the most of the useful commands.
# The check is repeated until timeout.
# 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_os_test_response_internal() {
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=$(mktemp /tmp/ct_test_response_XXXXXX)
local util_image_name='python:3.6'
ct_os_deploy_cmd_image "${util_image_name}"
while [ ${attempt} -le ${max_attempts} ]; do
ct_os_cmd_image_run "curl --connect-timeout 10 -s -w '%{http_code}' '${url}'" >${response_file} && status=0 || status=1
if [ ${status} -eq 0 ]; then
response_code=$(cat ${response_file} | tail -c 3)
if [ "${response_code}" -eq "${expected_code}" ]; then
result=0
fi
cat ${response_file} | grep -qP -e "${body_regexp}" || 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 -o ${attempt} -gt ${ignore_error_attempts} -o ${attempt} -eq ${max_attempts} ] ; then
break
fi
fi
attempt=$(( ${attempt} + 1 ))
sleep ${sleep_time}
done
rm -f ${response_file}
return ${result}
}
# ct_os_get_image_from_pod
# ------------------------
# Print image identifier from an existing pod to stdout
# Argument: pod_prefix - prefix or full name of the pod to get image from
ct_os_get_image_from_pod() {
local pod_prefix=$1 ; shift
local pod_name=$(ct_os_get_pod_name $pod_prefix)
oc get "po/${pod_name}" -o yaml | sed -ne 's/^\s*image:\s*\(.*\)\s*$/\1/ p' | head -1
}
# ct_os_check_cmd_internal
# ----------------
# Runs a specified command, checks exit code and compares the output with expected regexp.
# That all is done inside an image in the cluster, so the function is used
# typically in clusters that are not accessible outside.
# The check is repeated until timeout.
# Argument: util_image_name - name of the image in the cluster that is used for running the cmd
# Argument: service_name - kubernetes' service name to work with (IP address is taken from this one)
# Argument: check_command - command that is run within the util_image_name container
# Argument: expected_content_match - regexp that must be in the output (use .* to ignore check)
# Argument: timeout - number of seconds to wait till the check succeeds
function ct_os_check_cmd_internal() {
local util_image_name=$1 ; shift
local service_name=$1 ; shift
local check_command=$1 ; shift
local expected_content_match=${1:-.*} ; shift
local timeout=${1:-60} ; shift || :
: " Service ${service_name} check ..."
local output
local ret
local ip=$(ct_os_get_service_ip "${service_name}")
local check_command_exp=$(echo "$check_command" | sed -e "s/<IP>/$ip/g")
ct_os_deploy_cmd_image $(ct_os_get_image_from_pod "${util_image_name}" | head -n 1)
SECONDS=0
echo -n "Waiting for ${service_name} service becoming ready ..."
while true ; do
output=$(ct_os_cmd_image_run "$check_command_exp")
ret=$?
echo "${output}" | grep -qe "${expected_content_match}" || ret=1
if [ ${ret} -eq 0 ] ; then
echo " PASS"
return 0
fi
echo -n "."
[ ${SECONDS} -gt ${timeout} ] && break
sleep 3
done
echo " FAIL"
return 1
}

507
test/test-lib.sh Normal file
View file

@ -0,0 +1,507 @@
#
# Test a container image.
#
# Always use sourced from a specific container testfile
#
# reguires definition of CID_FILE_DIR
# CID_FILE_DIR=$(mktemp --suffix=<container>_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() {
for cid_file in $CID_FILE_DIR/* ; do
local 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_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
echo $(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-}"
CONTAINER_ARGS="$@"
ct_create_container $cid_file
if [ $? -eq 0 ]; then
local 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
[ ! -z $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
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=$(mktemp -d)
local f
: " Testing documentation in the container image"
# Extract the help files from the container
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 ! cat ${tmpdir}/$(basename ${f}) | grep -F -q -e "${term}" ; 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!"
}
# ct_npm_works
# --------------------
# Checks existance of the npm tool and runs it.
function ct_npm_works() {
local tmpdir=$(mktemp -d)
: " Testing npm in the container image"
docker run --rm ${IMAGE_NAME} /bin/bash -c "npm --version" >${tmpdir}/version
if [ $? -ne 0 ] ; then
echo "ERROR: 'npm --version' does not work inside the image ${IMAGE_NAME}." >&2
return 1
fi
docker run --rm ${IMAGE_NAME} /bin/bash -c "npm install jquery && test -f node_modules/jquery/src/jquery.js"
if [ $? -ne 0 ] ; then
echo "ERROR: npm could not install jquery inside the image ${IMAGE_NAME}." >&2
return 1
fi
: " Success!"
}
# 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"
[ -f test/$test_case ] && source test/$test_case
[ -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:
# <output_dir>/<base_name>-cert-selfsigned.pem -- public PEM cert
# <output_dir>/<base_name>-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=$(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=$(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=$(cat ${response_file} | tail -c 3)
if [ "${response_code}" -eq "${expected_code}" ]; then
result=0
fi
cat ${response_file} | grep -qP -e "${body_regexp}" || 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 -o ${attempt} -gt ${ignore_error_attempts} -o ${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
rhel7)
registry=registry.access.redhat.com
;;
*)
registry=docker.io
;;
esac
echo "$registry"
}
# 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=
# Use /tmp to not pollute cwd
tmpdir=$(mktemp -d)
df_name=$(mktemp -p "$tmpdir" Dockerfile.XXXX)
pushd "$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_id=$(docker inspect -f "{{.ContainerConfig.User}}" "$src_image")
# 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 <<EOF >"$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"
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
# Run the build and tag the result
docker build -f "$df_name" -t "$dst_image" .
popd
}