Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
phracek
aab5f7d02b Update build-and-push.yml 2021-09-16 12:41:48 +00:00
Petr "Stone" Hracek
4b47bc1408 Add suport sync for upstream -> downstream
Signed-off-by: Petr "Stone" Hracek <phracek@redhat.com>
2021-09-13 10:52:42 +02:00
32 changed files with 4183 additions and 0 deletions

1
6 Symbolic link
View file

@ -0,0 +1 @@
.

79
Dockerfile Normal file
View file

@ -0,0 +1,79 @@
FROM registry.fedoraproject.org/f33/s2i-core:latest
# Redis image based on Software Collections packages
#
# Volumes:
# * /var/lib/redis/data - Datastore for Redis
# Environment:
# * $REDIS_PASSWORD - Database password
ENV NAME=redis \
RELEASE=\"2" \
VERSION=6
ENV REDIS_VERSION=$VERSION \
HOME=/var/lib/redis
ENV SUMMARY="Redis in-memory data structure store, used as database, cache and message broker" \
DESCRIPTION="Redis $REDIS_VERSION available as container, is an advanced key-value store. \
It is often referred to as a data structure server since keys can contain strings, hashes, lists, \
sets and sorted sets. You can run atomic operations on these types, like appending to a string; \
incrementing the value in a hash; pushing to a list; computing set intersection, union and difference; \
or getting the member with highest ranking in a sorted set. In order to achieve its outstanding \
performance, Redis works with an in-memory dataset. Depending on your use case, you can persist \
it either by dumping the dataset to disk every once in a while, or by appending each command to a log."
LABEL summary="$SUMMARY" \
description="$DESCRIPTION" \
io.k8s.description="$SUMMARY" \
io.k8s.display-name="Redis 6" \
io.openshift.expose-services="6379:redis" \
io.openshift.tags="database,redis,redis6" \
com.redhat.component="$NAME" \
name="$FGC/$NAME" \
version="$VERSION" \
usage="docker run -d --name redis_database -p 6379:6379 $FGC/$NAME" \
maintainer="SoftwareCollections.org <sclorg@redhat.com>"
EXPOSE 6379
# Create user for redis that has known UID
# We need to do this before installing the RPMs which would create user with random UID
# The UID is the one used by the default user from the parent layer (1001),
# and since the user exists already, do not create a new one, but only rename
# the existing
# This image must forever use UID 1001 for redis user so our volumes are
# safe in the future. This should *never* change, the last test is there
# to make sure of that.
RUN getent group redis &> /dev/null || groupadd -r redis &> /dev/null && \
usermod -l redis -aG redis -c 'Redis Server' default &> /dev/null && \
# Install gettext for envsubst command
dnf install -y yum-utils gettext policycoreutils && \
INSTALL_PKGS="redis" && \
dnf install -y --setopt=tsflags=nodocs --nogpgcheck $INSTALL_PKGS && \
rpm -V $INSTALL_PKGS && \
dnf clean all && \
mkdir -p /var/lib/redis/data && chown -R redis.0 /var/lib/redis && \
[[ "$(id redis)" == "uid=1001(redis)"* ]]
# Get prefix path and path to scripts rather than hard-code them in scripts
ENV CONTAINER_SCRIPTS_PATH=/usr/share/container-scripts/redis \
REDIS_PREFIX=/usr
COPY root /
# this is needed due to issues with squash
# when this directory gets rm'd by the container-setup
# script.
RUN /usr/libexec/container-setup
VOLUME ["/var/lib/redis/data"]
# Using a numeric value because of a comment in [1]:
# If your S2I image does not include a USER declaration with a numeric user,
# your builds will fail by default.
# [1] https://docs.openshift.com/container-platform/4.4/openshift_images/create-images.html#images-create-guide-openshift_create-images
USER 1001
ENTRYPOINT ["container-entrypoint"]
CMD ["run-redis"]

1
Dockerfile.fedora Symbolic link
View file

@ -0,0 +1 @@
Dockerfile

1
README.md Symbolic link
View file

@ -0,0 +1 @@
root/usr/share/container-scripts/redis/README.md

20
bot-cfg.yml Normal file
View file

@ -0,0 +1,20 @@
---
version: "1"
betka:
# is betka enabled for this repository
# optional - defaults to true
enabled: true
notifications:
email_addresses: ["pkubat@redhat.com", "phracek@redhat.com", "hhorak@redhat.com"]
# Specify if master branch in upstream repository is synced
master_checker: true
# Should pull requests be synced?
pr_checker: false
# Path to directory with dockerfile withing upstream repository
upstream_git_path: "6"
# Github comment message to enforce sync of a pull request
pr_comment_message: "[test]"
# Specify URL to an image used for dist-git source generation. Like
image_url: "quay.io/rhscl/cwt-generator"

1
help.md Symbolic link
View file

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

View file

@ -0,0 +1,2 @@
#!/bin/bash
exec "$@"

27
root/usr/bin/run-redis Executable file
View file

@ -0,0 +1,27 @@
#!/bin/bash
export_vars=$(cgroup-limits); export $export_vars
source ${CONTAINER_SCRIPTS_PATH}/common.sh
set -eu
[ -f ${CONTAINER_SCRIPTS_PATH}/validate-variables.sh ] && source ${CONTAINER_SCRIPTS_PATH}/validate-variables.sh
# Process the Redis configuration files
log_info 'Processing Redis configuration files ...'
if [[ -v REDIS_PASSWORD ]]; then
envsubst < ${CONTAINER_SCRIPTS_PATH}/password.conf.template >> /etc/redis/redis.conf
else
log_info 'WARNING: setting REDIS_PASSWORD is recommended'
fi
# Source post-init source if exists
if [ -f ${CONTAINER_SCRIPTS_PATH}/post-init.sh ]; then
log_info 'Sourcing post-init.sh ...'
source ${CONTAINER_SCRIPTS_PATH}/post-init.sh
fi
# Restart the Redis server with public IP bindings
unset_env_vars
log_volume_info "${REDIS_DATADIR}"
log_info 'Running final exec -- Only Redis logs after this point'
exec ${REDIS_PREFIX}/bin/redis-server /etc/redis/redis.conf --daemonize no "$@" 2>&1

4
root/usr/bin/usage Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
cat /usr/share/container-scripts/redis/README.md

View file

@ -0,0 +1,31 @@
#!/bin/bash
source ${CONTAINER_SCRIPTS_PATH}/common.sh
set -eu
# setup config file
if [ -n "${ENABLED_COLLECTIONS:-}" ] ; then
mv /etc/opt/rh/rh-redis6/redis.conf /etc/redis/redis.conf
ln -s /etc/redis/redis.conf /etc/opt/rh/rh-redis6/redis.conf
fi
# setup directory for data
chown -R redis:0 "${HOME}" /etc/redis/redis.conf
restorecon -R "${HOME}" /etc/redis/redis.conf
# create a symlink for SCL datadir, so there is some reasonable content there
if [ -n "${ENABLED_COLLECTIONS:-}" ] ; then
rmdir /var/opt/rh/rh-redis6/lib/redis/
ln -s /var/lib/redis /var/opt/rh/rh-redis6/lib/redis
fi
# Loosen permission bits for group to avoid problems running container with
# arbitrary UID
# When only specifying user, group is 0, that's why /var/lib/redis must have
# owner redis.0; that allows to avoid a+rwx for this dir
chmod 0770 "${HOME}" "${REDIS_DATADIR}"
chmod 0660 /etc/redis/redis.conf
# adjust config with changes we do every-time
clear_config
envsubst < ${CONTAINER_SCRIPTS_PATH}/base.conf.template >> /etc/redis/redis.conf

View file

@ -0,0 +1,85 @@
Redis 6 in-memory data structure store container image
======================================================
This container image includes Redis 6 in-memory data structure store for OpenShift and general usage.
Users can choose between RHEL, CentOS and Fedora based images.
The RHEL images are available in the [Red Hat Container Catalog](https://access.redhat.com/containers/),
the CentOS images are available on [Quay.io](https://quay.io/organization/centos7),
and the Fedora images are available in [Fedora Registry](https://registry.fedoraproject.org/).
The resulting image can be run using [podman](https://github.com/containers/libpod).
Note: while the examples in this README are calling `podman`, you can replace any such calls by `docker` with the same arguments
Description
-----------
Redis 6 available as container, is an advanced key-value store.
It is often referred to as a data structure server since keys can contain strings, hashes, lists,
sets and sorted sets. You can run atomic operations on these types, like appending to a string;
incrementing the value in a hash; pushing to a list; computing set intersection, union and difference;
or getting the member with highest ranking in a sorted set. In order to achieve its outstanding
performance, Redis works with an in-memory dataset. Depending on your use case, you can persist
it either by dumping the dataset to disk every once in a while, or by appending each command to a log.
Usage
-----
For this, we will assume that you are using the `rhel8/redis-6` image.
If you want to set only the mandatory environment variables and not store
the database in a host directory, execute the following command:
```
$ podman run -d --name redis_database -p 6379:6379 rhel8/redis-6
```
This will create a container named `redis_database`. Port 6379 will be exposed and mapped
to the host.
If you want your database to be persistent across container executions, also add a
`-v /host/db/path:/var/lib/redis/data:Z` argument. This will be the Redis data directory.
For protecting Redis data by a password, pass `REDIS_PASSWORD` environment variable
to the container like this:
```
$ podman run -d --name redis_database -e REDIS_PASSWORD=strongpassword rhel8/redis-6
```
**Warning: since Redis is pretty fast an outside user can try up to
150k passwords per second against a good box. This means that you should
use a very strong password otherwise it will be very easy to break.**
Environment variables and volumes
----------------------------------
**`REDIS_PASSWORD`**
Password for the server access
You can also set the following mount points by passing the `-v /host:/container:Z` flag to podman.
**`/var/lib/redis/data`**
Redis data directory
**Notice: When mouting a directory from the host into the container, ensure that the mounted
directory has the appropriate permissions and that the owner and group of the directory
matches the user UID or name which is running inside the container.**
Troubleshooting
---------------
Redis logs into standard output, so the log is available in the container log. The log can be examined by running:
podman logs <container>
See also
--------
Dockerfile and other sources for this container image are available on
https://github.com/sclorg/redis-container.
In that repository you also can find another versions of Python environment Dockerfiles.
Dockerfile for CentOS is called `Dockerfile`, Dockerfile for RHEL7 is called `Dockerfile.rhel7`,
for RHEL8 it's `Dockerfile.rhel8` and the Fedora Dockerfile is called Dockerfile.fedora.

View file

@ -0,0 +1 @@
dir ${REDIS_DATADIR}

View file

@ -0,0 +1,26 @@
#!/bin/bash
source ${CONTAINER_SCRIPTS_PATH}/helpers.sh
# Data directory where Redis database files live. The data subdirectory is here
# because .bashrc lives in /var/lib/redis/ and we don't want a
# volume to override it.
export REDIS_DATADIR=/var/lib/redis/data
# Be paranoid and stricter than we should be.
redis_password_regex='^[a-zA-Z0-9_~!@#$%^&*()-=<>,.?;:|]+$'
# Make sure env variables don't propagate to redis process.
function unset_env_vars() {
log_info 'Cleaning up environment variable REDIS_PASSWORD ...'
unset REDIS_PASSWORD
}
# Comment out settings that we'll set in container specifically
function clear_config() {
sed -e "s/^bind/#bind/" \
-e "s/^logfile/#logfile/" \
-e "s/^dir /#dir /" \
-e "/^protected-mode/s/yes/no/" \
-i /etc/redis/redis.conf
}

View file

@ -0,0 +1,24 @@
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
}

View file

@ -0,0 +1,3 @@
# password for the server
requirepass "${REDIS_PASSWORD}"

View file

@ -0,0 +1,6 @@
# This file serves for extending the container image, typically by changing
# the configuration, loading some data etc.
# Feel free to add content to this file or rewrite it at all.
# You may also start redis server locally to load some data for example,
# but do not forget to stop it after it, so it can be restarted after it.

View file

@ -0,0 +1,3 @@
# This will make scl collection binaries work out of box.
unset BASH_ENV PROMPT_COMMAND ENV
source scl_source enable ${ENABLED_COLLECTIONS}

View file

@ -0,0 +1,15 @@
function usage() {
[ $# == 1 ] && echo "error: $1"
echo "You can specify the following environment variables:"
echo " REDIS_PASSWORD (regex: '$redis_password_regex')"
exit 1
}
function validate_variables() {
# Check basic sanity of specified variables
if [[ -v REDIS_PASSWORD ]]; then
[[ "$REDIS_PASSWORD" =~ $redis_password_regex ]] || usage "Invalid password"
fi
}
validate_variables

View file

@ -0,0 +1,216 @@
{
"kind": "Template",
"apiVersion": "v1",
"metadata": {
"name": "redis-ephemeral",
"annotations": {
"openshift.io/display-name": "Redis (Ephemeral)",
"description": "Redis in-memory data structure store, without persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/redis-container/blob/master/5.\n\nWARNING: Any data stored will be lost upon pod destruction. Only use this template for testing",
"iconClass": "icon-redis",
"tags": "database,redis",
"openshift.io/long-description": "This template provides a standalone Redis server. The data is not stored on persistent storage, so any restart of the service will result in all data being lost.",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"openshift.io/documentation-url": "https://github.com/sclorg/redis-container/tree/master/5",
"openshift.io/support-url": "https://access.redhat.com"
}
},
"message": "The following service(s) have been created in your project: ${DATABASE_SERVICE_NAME}.\n\n Password: ${REDIS_PASSWORD}\n Connection URL: redis://${DATABASE_SERVICE_NAME}:6379/\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/sclorg/redis-container/blob/master/5.",
"labels": {
"template": "redis-ephemeral-template"
},
"objects": [
{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "${DATABASE_SERVICE_NAME}",
"annotations": {
"template.openshift.io/expose-password": "{.data['database-password']}"
}
},
"stringData" : {
"database-password" : "${REDIS_PASSWORD}"
}
},
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "${DATABASE_SERVICE_NAME}",
"annotations": {
"template.openshift.io/expose-uri": "redis://{.spec.clusterIP}:{.spec.ports[?(.name==\"redis\")].port}"
}
},
"spec": {
"ports": [
{
"name": "redis",
"protocol": "TCP",
"port": 6379,
"targetPort": 6379,
"nodePort": 0
}
],
"selector": {
"name": "${DATABASE_SERVICE_NAME}"
},
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "DeploymentConfig",
"apiVersion": "v1",
"metadata": {
"name": "${DATABASE_SERVICE_NAME}",
"annotations": {
"template.alpha.openshift.io/wait-for-ready": "true"
}
},
"spec": {
"strategy": {
"type": "Recreate"
},
"triggers": [
{
"type": "ImageChange",
"imageChangeParams": {
"automatic": true,
"containerNames": [
"redis"
],
"from": {
"kind": "ImageStreamTag",
"name": "redis:${REDIS_VERSION}",
"namespace": "${NAMESPACE}"
},
"lastTriggeredImage": ""
}
},
{
"type": "ConfigChange"
}
],
"replicas": 1,
"selector": {
"name": "${DATABASE_SERVICE_NAME}"
},
"template": {
"metadata": {
"labels": {
"name": "${DATABASE_SERVICE_NAME}"
}
},
"spec": {
"containers": [
{
"name": "redis",
"image": " ",
"ports": [
{
"containerPort": 6379,
"protocol": "TCP"
}
],
"readinessProbe": {
"timeoutSeconds": 1,
"initialDelaySeconds": 5,
"exec": {
"command": [ "/bin/sh", "-i", "-c", "test \"$(redis-cli -h 127.0.0.1 -a $REDIS_PASSWORD ping)\" == \"PONG\""]
}
},
"livenessProbe": {
"timeoutSeconds": 1,
"initialDelaySeconds": 30,
"tcpSocket": {
"port": 6379
}
},
"env": [
{
"name": "REDIS_PASSWORD",
"valueFrom": {
"secretKeyRef" : {
"name" : "${DATABASE_SERVICE_NAME}",
"key" : "database-password"
}
}
}
],
"resources": {
"limits": {
"memory": "${MEMORY_LIMIT}"
}
},
"volumeMounts": [
{
"name": "${DATABASE_SERVICE_NAME}-data",
"mountPath": "/var/lib/redis/data"
}
],
"terminationMessagePath": "/dev/termination-log",
"imagePullPolicy": "IfNotPresent",
"capabilities": {},
"securityContext": {
"capabilities": {},
"privileged": false
}
}
],
"volumes": [
{
"name": "${DATABASE_SERVICE_NAME}-data",
"emptyDir": {
"medium": ""
}
}
],
"restartPolicy": "Always",
"dnsPolicy": "ClusterFirst"
}
}
},
"status": {}
}
],
"parameters": [
{
"name": "MEMORY_LIMIT",
"displayName": "Memory Limit",
"description": "Maximum amount of memory the container can use.",
"value": "512Mi",
"required": true
},
{
"name": "NAMESPACE",
"displayName": "Namespace",
"description": "The OpenShift Namespace where the ImageStream resides.",
"value": "openshift"
},
{
"name": "DATABASE_SERVICE_NAME",
"displayName": "Database Service Name",
"description": "The name of the OpenShift Service exposed for the database.",
"value": "redis",
"required": true
},
{
"name": "REDIS_PASSWORD",
"displayName": "Redis Connection Password",
"description": "Password for the Redis connection user.",
"generate": "expression",
"from": "[a-zA-Z0-9]{16}",
"required": true
},
{
"name": "REDIS_VERSION",
"displayName": "Version of Redis Image",
"description": "Version of Redis image to be used (5-el7, 5-el8, or latest).",
"value": "5-el8",
"required": true
}
]
}

View file

@ -0,0 +1,240 @@
{
"kind": "Template",
"apiVersion": "v1",
"metadata": {
"name": "redis-persistent",
"annotations": {
"openshift.io/display-name": "Redis",
"description": "Redis in-memory data structure store, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/redis-container/blob/master/5.\n\nNOTE: You must have persistent volumes available in your cluster to use this template.",
"iconClass": "icon-redis",
"tags": "database,redis",
"openshift.io/long-description": "This template provides a standalone Redis server. The data is stored on persistent storage.",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"openshift.io/documentation-url": "https://github.com/sclorg/redis-container/tree/master/5",
"openshift.io/support-url": "https://access.redhat.com"
}
},
"message": "The following service(s) have been created in your project: ${DATABASE_SERVICE_NAME}.\n\n Password: ${REDIS_PASSWORD}\n Connection URL: redis://${DATABASE_SERVICE_NAME}:6379/\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/sclorg/redis-container/blob/master/5.",
"labels": {
"template": "redis-persistent-template"
},
"objects": [
{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "${DATABASE_SERVICE_NAME}",
"annotations": {
"template.openshift.io/expose-password": "{.data['database-password']}"
}
},
"stringData" : {
"database-password" : "${REDIS_PASSWORD}"
}
},
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "${DATABASE_SERVICE_NAME}",
"annotations": {
"template.openshift.io/expose-uri": "redis://{.spec.clusterIP}:{.spec.ports[?(.name==\"redis\")].port}"
}
},
"spec": {
"ports": [
{
"name": "redis",
"protocol": "TCP",
"port": 6379,
"targetPort": 6379,
"nodePort": 0
}
],
"selector": {
"name": "${DATABASE_SERVICE_NAME}"
},
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "PersistentVolumeClaim",
"apiVersion": "v1",
"metadata": {
"name": "${DATABASE_SERVICE_NAME}"
},
"spec": {
"accessModes": [
"ReadWriteOnce"
],
"resources": {
"requests": {
"storage": "${VOLUME_CAPACITY}"
}
}
}
},
{
"kind": "DeploymentConfig",
"apiVersion": "v1",
"metadata": {
"name": "${DATABASE_SERVICE_NAME}",
"annotations": {
"template.alpha.openshift.io/wait-for-ready": "true"
}
},
"spec": {
"strategy": {
"type": "Recreate"
},
"triggers": [
{
"type": "ImageChange",
"imageChangeParams": {
"automatic": true,
"containerNames": [
"redis"
],
"from": {
"kind": "ImageStreamTag",
"name": "redis:${REDIS_VERSION}",
"namespace": "${NAMESPACE}"
},
"lastTriggeredImage": ""
}
},
{
"type": "ConfigChange"
}
],
"replicas": 1,
"selector": {
"name": "${DATABASE_SERVICE_NAME}"
},
"template": {
"metadata": {
"labels": {
"name": "${DATABASE_SERVICE_NAME}"
}
},
"spec": {
"containers": [
{
"name": "redis",
"image": " ",
"ports": [
{
"containerPort": 6379,
"protocol": "TCP"
}
],
"readinessProbe": {
"timeoutSeconds": 1,
"initialDelaySeconds": 5,
"exec": {
"command": [ "/bin/sh", "-i", "-c", "test \"$(redis-cli -h 127.0.0.1 -a $REDIS_PASSWORD ping)\" == \"PONG\""]
}
},
"livenessProbe": {
"timeoutSeconds": 1,
"initialDelaySeconds": 30,
"tcpSocket": {
"port": 6379
}
},
"env": [
{
"name": "REDIS_PASSWORD",
"valueFrom": {
"secretKeyRef" : {
"name" : "${DATABASE_SERVICE_NAME}",
"key" : "database-password"
}
}
}
],
"resources": {
"limits": {
"memory": "${MEMORY_LIMIT}"
}
},
"volumeMounts": [
{
"name": "${DATABASE_SERVICE_NAME}-data",
"mountPath": "/var/lib/redis/data"
}
],
"terminationMessagePath": "/dev/termination-log",
"imagePullPolicy": "IfNotPresent",
"capabilities": {},
"securityContext": {
"capabilities": {},
"privileged": false
}
}
],
"volumes": [
{
"name": "${DATABASE_SERVICE_NAME}-data",
"persistentVolumeClaim": {
"claimName": "${DATABASE_SERVICE_NAME}"
}
}
],
"restartPolicy": "Always",
"dnsPolicy": "ClusterFirst"
}
}
},
"status": {}
}
],
"parameters": [
{
"name": "MEMORY_LIMIT",
"displayName": "Memory Limit",
"description": "Maximum amount of memory the container can use.",
"value": "512Mi",
"required": true
},
{
"name": "NAMESPACE",
"displayName": "Namespace",
"description": "The OpenShift Namespace where the ImageStream resides.",
"value": "openshift"
},
{
"name": "DATABASE_SERVICE_NAME",
"displayName": "Database Service Name",
"description": "The name of the OpenShift Service exposed for the database.",
"value": "redis",
"required": true
},
{
"name": "REDIS_PASSWORD",
"displayName": "Redis Connection Password",
"description": "Password for the Redis connection user.",
"generate": "expression",
"from": "[a-zA-Z0-9]{16}",
"required": true
},
{
"name": "VOLUME_CAPACITY",
"displayName": "Volume Capacity",
"description": "Volume space available for data, e.g. 512Mi, 2Gi.",
"value": "1Gi",
"required": true
},
{
"name": "REDIS_VERSION",
"displayName": "Version of Redis Image",
"description": "Version of Redis image to be used (5-el7, 5-el8, or latest).",
"value": "5-el8",
"required": true
}
]
}

View file

@ -0,0 +1,85 @@
{
"apiVersion": "v1",
"kind": "ImageStream",
"metadata": {
"annotations": {
"openshift.io/display-name": "Redis"
},
"name": "redis"
},
"spec": {
"tags": [
{
"annotations": {
"description": "Provides a Redis database on CentOS. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/5/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Redis available on OpenShift, including major version updates.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis (Latest)",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis"
},
"from": {
"kind": "ImageStreamTag",
"name": "5-el8"
},
"referencePolicy": {
"type": "Local"
},
"name": "latest"
},
{
"annotations": {
"description": "Provides a Redis 5 database on CentOS 8. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/5/README.md.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis 5 (CentOS 8)",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis",
"version": "5"
},
"from": {
"kind": "DockerImage",
"name": "docker.io/centos/redis-5-centos8:latest"
},
"referencePolicy": {
"type": "Local"
},
"name": "5-el8"
},
{
"annotations": {
"description": "Provides a Redis 5 database on CentOS 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/5/README.md.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis 5 (CentOS 7)",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis",
"version": "5"
},
"from": {
"kind": "DockerImage",
"name": "quay.io/centos7/redis-5-centos7:latest"
},
"referencePolicy": {
"type": "Local"
},
"name": "5-el7"
},
{
"annotations": {
"description": "Provides a Redis 5 database on CentOS 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/5/README.md.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis 5",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis,hidden",
"version": "5"
},
"from": {
"kind": "DockerImage",
"name": "quay.io/centos7/redis-5-centos7:latest"
},
"referencePolicy": {
"type": "Local"
},
"name": "5"
}
]
}
}

View file

@ -0,0 +1,67 @@
{
"apiVersion": "v1",
"kind": "ImageStream",
"metadata": {
"annotations": {
"openshift.io/display-name": "Redis"
},
"name": "redis"
},
"spec": {
"tags": [
{
"annotations": {
"description": "Provides a Redis database on RHEL. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/6/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Redis available on OpenShift, including major version updates.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis (Latest)",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis"
},
"from": {
"kind": "ImageStreamTag",
"name": "6-el8"
},
"referencePolicy": {
"type": "Local"
},
"name": "latest"
},
{
"annotations": {
"description": "Provides a Redis 6 database on RHEL 8. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/6/README.md.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis 6 (RHEL 8)",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis",
"version": "6"
},
"from": {
"kind": "DockerImage",
"name": "registry.redhat.io/rhel8/redis-6:latest"
},
"referencePolicy": {
"type": "Local"
},
"name": "6-el8"
},
{
"annotations": {
"description": "Provides a Redis 5 database on RHEL 8. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/5/README.md.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis 5 (RHEL 8)",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis",
"version": "5"
},
"from": {
"kind": "DockerImage",
"name": "registry.redhat.io/rhel8/redis-5:latest"
},
"referencePolicy": {
"type": "Local"
},
"name": "5-el8"
}
]
}
}

View file

@ -0,0 +1,103 @@
{
"apiVersion": "v1",
"kind": "ImageStream",
"metadata": {
"annotations": {
"openshift.io/display-name": "Redis"
},
"name": "redis"
},
"spec": {
"tags": [
{
"annotations": {
"description": "Provides a Redis database on RHEL. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/6/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Redis available on OpenShift, including major version updates.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis (Latest)",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis"
},
"from": {
"kind": "ImageStreamTag",
"name": "6-el8"
},
"referencePolicy": {
"type": "Local"
},
"name": "latest"
},
{
"annotations": {
"description": "Provides a Redis 6 database on RHEL 8. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/6/README.md.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis 6 (RHEL 8)",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis",
"version": "6"
},
"from": {
"kind": "DockerImage",
"name": "registry.redhat.io/rhel8/redis-6:latest"
},
"referencePolicy": {
"type": "Local"
},
"name": "6-el8"
},
{
"annotations": {
"description": "Provides a Redis 5 database on RHEL 8. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/5/README.md.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis 5 (RHEL 8)",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis",
"version": "5"
},
"from": {
"kind": "DockerImage",
"name": "registry.redhat.io/rhel8/redis-5:latest"
},
"referencePolicy": {
"type": "Local"
},
"name": "5-el8"
},
{
"annotations": {
"description": "Provides a Redis 5 database on RHEL 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/5/README.md.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis 5 (RHEL 7)",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis",
"version": "5"
},
"from": {
"kind": "DockerImage",
"name": "registry.redhat.io/rhscl/redis-5-rhel7:latest"
},
"referencePolicy": {
"type": "Local"
},
"name": "5-el7"
},
{
"annotations": {
"description": "Provides a Redis 5 database on RHEL 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/5/README.md.",
"iconClass": "icon-redis",
"openshift.io/display-name": "Redis 5",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"tags": "redis,hidden",
"version": "5"
},
"from": {
"kind": "DockerImage",
"name": "registry.redhat.io/rhscl/redis-5-rhel7:latest"
},
"referencePolicy": {
"type": "Local"
},
"name": "5"
}
]
}
}

View file

@ -0,0 +1,216 @@
{
"kind": "Template",
"apiVersion": "v1",
"metadata": {
"name": "redis-ephemeral",
"annotations": {
"openshift.io/display-name": "Redis (Ephemeral)",
"description": "Redis in-memory data structure store, without persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/redis-container/blob/master/5.\n\nWARNING: Any data stored will be lost upon pod destruction. Only use this template for testing",
"iconClass": "icon-redis",
"tags": "database,redis",
"openshift.io/long-description": "This template provides a standalone Redis server. The data is not stored on persistent storage, so any restart of the service will result in all data being lost.",
"openshift.io/provider-display-name": "Red Hat, Inc.",
"openshift.io/documentation-url": "https://github.com/sclorg/redis-container/tree/master/5",
"openshift.io/support-url": "https://access.redhat.com"
}
},
"message": "The following service(s) have been created in your project: ${DATABASE_SERVICE_NAME}.\n\n Password: ${REDIS_PASSWORD}\n Connection URL: redis://${DATABASE_SERVICE_NAME}:6379/\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/sclorg/redis-container/blob/master/5.",
"labels": {
"template": "redis-ephemeral-template"
},
"objects": [
{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "${DATABASE_SERVICE_NAME}",
"annotations": {
"template.openshift.io/expose-password": "{.data['database-password']}"
}
},
"stringData" : {
"database-password" : "${REDIS_PASSWORD}"
}
},
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "${DATABASE_SERVICE_NAME}",
"annotations": {
"template.openshift.io/expose-uri": "redis://{.spec.clusterIP}:{.spec.ports[?(.name==\"redis\")].port}"
}
},
"spec": {
"ports": [
{
"name": "redis",
"protocol": "TCP",
"port": 6379,
"targetPort": 6379,
"nodePort": 0
}
],
"selector": {
"name": "${DATABASE_SERVICE_NAME}"
},
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "DeploymentConfig",
"apiVersion": "v1",
"metadata": {
"name": "${DATABASE_SERVICE_NAME}",
"annotations": {
"template.alpha.openshift.io/wait-for-ready": "true"
}
},
"spec": {
"strategy": {
"type": "Recreate"
},
"triggers": [
{
"type": "ImageChange",
"imageChangeParams": {
"automatic": true,
"containerNames": [
"redis"
],
"from": {
"kind": "ImageStreamTag",
"name": "redis:${REDIS_VERSION}",
"namespace": "${NAMESPACE}"
},
"lastTriggeredImage": ""
}
},
{
"type": "ConfigChange"
}
],
"replicas": 1,
"selector": {
"name": "${DATABASE_SERVICE_NAME}"
},
"template": {
"metadata": {
"labels": {
"name": "${DATABASE_SERVICE_NAME}"
}
},
"spec": {
"containers": [
{
"name": "redis",
"image": " ",
"ports": [
{
"containerPort": 6379,
"protocol": "TCP"
}
],
"readinessProbe": {
"timeoutSeconds": 1,
"initialDelaySeconds": 5,
"exec": {
"command": [ "/bin/sh", "-i", "-c", "test \"$(redis-cli -h 127.0.0.1 -a $REDIS_PASSWORD ping)\" == \"PONG\""]
}
},
"livenessProbe": {
"timeoutSeconds": 1,
"initialDelaySeconds": 30,
"tcpSocket": {
"port": 6379
}
},
"env": [
{
"name": "REDIS_PASSWORD",
"valueFrom": {
"secretKeyRef" : {
"name" : "${DATABASE_SERVICE_NAME}",
"key" : "database-password"
}
}
}
],
"resources": {
"limits": {
"memory": "${MEMORY_LIMIT}"
}
},
"volumeMounts": [
{
"name": "${DATABASE_SERVICE_NAME}-data",
"mountPath": "/var/lib/redis/data"
}
],
"terminationMessagePath": "/dev/termination-log",
"imagePullPolicy": "IfNotPresent",
"capabilities": {},
"securityContext": {
"capabilities": {},
"privileged": false
}
}
],
"volumes": [
{
"name": "${DATABASE_SERVICE_NAME}-data",
"emptyDir": {
"medium": ""
}
}
],
"restartPolicy": "Always",
"dnsPolicy": "ClusterFirst"
}
}
},
"status": {}
}
],
"parameters": [
{
"name": "MEMORY_LIMIT",
"displayName": "Memory Limit",
"description": "Maximum amount of memory the container can use.",
"value": "512Mi",
"required": true
},
{
"name": "NAMESPACE",
"displayName": "Namespace",
"description": "The OpenShift Namespace where the ImageStream resides.",
"value": "openshift"
},
{
"name": "DATABASE_SERVICE_NAME",
"displayName": "Database Service Name",
"description": "The name of the OpenShift Service exposed for the database.",
"value": "redis",
"required": true
},
{
"name": "REDIS_PASSWORD",
"displayName": "Redis Connection Password",
"description": "Password for the Redis connection user.",
"generate": "expression",
"from": "[a-zA-Z0-9]{16}",
"required": true
},
{
"name": "REDIS_VERSION",
"displayName": "Version of Redis Image",
"description": "Version of Redis image to be used (5-el7, 5-el8, or latest).",
"value": "5-el8",
"required": true
}
]
}

359
test/run Executable file
View file

@ -0,0 +1,359 @@
#!/bin/bash
#
# Test the Redis image.
#
# IMAGE_NAME specifies the name of the candidate image used for testing.
# The image has to be available before this script is executed.
#
set -o errexit
set -o nounset
shopt -s nullglob
[ "${DEBUG:-0}" -eq 1 ] && set -x
test -n "${IMAGE_NAME-}" || { echo 'make sure $IMAGE_NAME is defined' && false ;}
test -n "${VERSION-}" || { echo 'make sure $VERSION is defined' && false; }
test -n "${OS-}" || { echo 'make sure $OS is defined' && false; }
test_short_summary=''
TESTSUITE_RESULT=0
TEST_LIST="\
run_container_creation_tests
run_tests_no_root
run_tests_no_pass
run_tests_no_pass_altuid
run_tests_no_root_altuid
run_change_password_test
run_doc_test
"
CIDFILE_DIR=$(mktemp --suffix=redis_test_cidfiles -d)
function cleanup() {
local cidfile
for cidfile in $CIDFILE_DIR/* ; do
local CONTAINER
CONTAINER=$(cat $cidfile)
echo "Stopping and removing container $CONTAINER..."
docker stop $CONTAINER >/dev/null
local exit_status
exit_status=$(docker inspect -f '{{.State.ExitCode}}' $CONTAINER)
if [ "$exit_status" != "0" ]; then
echo "Inspecting container $CONTAINER"
docker inspect $CONTAINER
echo "Dumping logs for $CONTAINER"
docker logs $CONTAINER
fi
docker rm -v $CONTAINER >/dev/null
rm $cidfile
echo "Done."
done
rmdir $CIDFILE_DIR
echo "$test_short_summary"
if [ $TESTSUITE_RESULT -eq 0 ] ; then
echo "Tests for ${IMAGE_NAME} succeeded."
else
echo "Tests for ${IMAGE_NAME} failed."
fi
exit $TESTSUITE_RESULT
}
trap cleanup EXIT SIGINT
check_result() {
local result="$1"
if [[ "$result" != "0" ]]; then
TESTCASE_RESULT=1
fi
return $result
}
function get_cid() {
local id="$1" ; shift || return 1
echo $(cat "$CIDFILE_DIR/$id")
}
function get_container_ip() {
local id="$1" ; shift
docker inspect --format='{{.NetworkSettings.IPAddress}}' $(get_cid "$id")
}
function connection_works() {
local container_ip="$1"; shift
local password="$1"; shift
if [ "$(redis_cmd "$container_ip" "$password" ping)" == "PONG" ] ; then
return 0
fi
return 1
}
function redis_cmd() {
local container_ip="$1"; shift
local password="$1"; shift
# if empty password is given, then no password will be specified
docker run --rm "$IMAGE_NAME" redis-cli -h "$container_ip" ${password:+-a "$password"} "$@"
}
function test_connection() {
local name=$1 ; shift
local password=$1 ; shift
local ip
ip=$(get_container_ip $name)
echo " Testing Redis connection to $ip (password='${password:-}')..."
local max_attempts=10
local sleep_time=2
local i
for i in $(seq $max_attempts); do
echo " Trying to connect..."
if connection_works "$ip" "$password" ; then
echo " Success!"
echo
return 0
fi
sleep $sleep_time
done
echo " Giving up: Failed to connect. Logs:"
docker logs $(get_cid $name)
return 1
}
function test_redis() {
local container_ip="$1"
local password="$2"
echo " Testing Redis (password='${password:-}')"
redis_cmd "$container_ip" "$password" set a 1 >/dev/null
check_result $?
redis_cmd "$container_ip" "$password" set b 2 >/dev/null
check_result $?
test "$(redis_cmd "$container_ip" "$password" get b)" == '2'
echo " Success!"
echo
}
function create_container() {
local name=$1 ; shift
cidfile="$CIDFILE_DIR/$name"
# create container with a cidfile in a directory for cleanup
local container_id
[ "${DEBUG:-0}" -eq 1 ] && echo "DEBUG: docker run ${DOCKER_ARGS:-} --cidfile $cidfile -d \"$@\" $IMAGE_NAME ${CONTAINER_ARGS:-}" >&2
container_id="$(docker run ${DOCKER_ARGS:-} --cidfile $cidfile -d "$@" $IMAGE_NAME ${CONTAINER_ARGS:-})"
[ "${DEBUG:-0}" -eq 1 ] && echo "Created container $container_id"
[ x"$container_id" == "x" ] && return 1 || return 0
}
function run_change_password_test() {
local tmpdir=$(mktemp -d)
mkdir "${tmpdir}/data" && chmod -R a+rwx "${tmpdir}"
# Create Redis container with persistent volume and set the initial password
create_container "testpass1" -e REDIS_PASSWORD=foo \
-v ${tmpdir}:/var/lib/redis/data:Z
check_result $?
test_connection testpass1 foo
check_result $?
docker stop $(get_cid testpass1) >/dev/null
# Create second container with changed password
create_container "testpass2" -e REDIS_PASSWORD=bar \
-v ${tmpdir}:/var/lib/redis/data:Z
check_result $?
test_connection testpass2 bar
check_result $?
# The old password should not work anymore
container_ip="$(get_container_ip testpass2)"
connection_works "$container_ip" foo
check_result $?
}
function assert_login_access() {
local container_ip=$1; shift
local PASS=$1 ; shift
local success=$1 ; shift
if connection_works "$container_ip" "$PASS" ; then
if $success ; then
echo " Connection ($PASS) access granted as expected"
return 0
fi
else
if ! $success ; then
echo " Connection ($PASS) access denied as expected"
return 0
fi
fi
echo " Connection ($PASS) login assertion failed"
return 1
}
function assert_local_access() {
local id="$1" ; shift
docker exec $(get_cid "$id") bash -c 'redis-cli ping'
}
# Make sure the invocation of docker run fails.
function assert_container_creation_fails() {
# Time the docker run command. It should fail. If it doesn't fail,
# redis will keep running so we kill it with SIGKILL to make sure
# timeout returns a non-zero value.
local ret=0
timeout -s 9 --preserve-status 60s docker run --rm "$@" $IMAGE_NAME >/dev/null || ret=$?
# Timeout will exit with a high number.
if [ $ret -gt 10 ]; then
return 1
fi
}
function try_image_invalid_combinations() {
assert_container_creation_fails -e REDIS_PASSWORD="pass with space" "$@"
check_result $?
}
function run_container_creation_tests() {
local ret
echo " Testing image entrypoint usage"
try_image_invalid_combinations
ret=$?
if [ $ret -eq 0 ]; then
echo " Success!"
else
echo " Failed!"
fi
echo
return $ret
}
test_scl_usage() {
local name="$1"
local run_cmd="$2"
local expected="$3"
echo " Testing the image SCL enable"
local out
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 $(get_cid $name) /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 $(get_cid $name) /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
}
run_doc_test() {
local tmpdir=$(mktemp -d)
local f
echo " Testing documentation in the container image"
# Extract the help.1 file from the container
docker run --rm ${IMAGE_NAME} /bin/bash -c "cat /help.1" >${tmpdir}/help.1
# Check whether the help.1 file includes some important information
for term in 6379 "REDIS\_PASSWORD" volume; do
if ! cat ${tmpdir}/help.1 | grep -F -q -e "${term}" ; then
echo "ERROR: File /help.1 does not include '${term}'."
return 1
fi
done
# Check whether the file uses the correct format
if ! file ${tmpdir}/help.1 | grep -q roff ; then
echo "ERROR: /help.1 is not in troff or groff format"
return 1
fi
echo " Success!"
echo
}
function run_tests() {
local name=$1 ; shift
local ret
envs=${PASS:+"-e REDIS_PASSWORD=$PASS"}
PASS=${PASS:-}
create_container $name $envs
ret=$?
check_result $ret
test_connection "$name" "$PASS"
ret=$?
check_result $ret
# Only check version on rhel/centos builds
if [ "$OS" != "fedora" ]; then
echo " Testing scl usage"
test_scl_usage $name 'redis-server --version' "$VERSION"
check_result $?
fi
echo " Testing login accesses"
local container_ip
container_ip=$(get_container_ip $name)
assert_login_access "$container_ip" "$PASS" true
ret=$?
check_result $ret
if [ -n "$PASS" ] ; then
assert_login_access "$container_ip" "${PASS}_foo" false
check_result $?
fi
assert_local_access "$name"
ret=$?
check_result $ret
if [ $ret -ne 0 ]; then
echo " Local access FAILED."
else
echo " Local access SUCCESS."
fi
echo
test_redis "$container_ip" "$PASS"
check_result $?
}
function run_tests_no_root() {
# Normal tests with password
PASS=pass run_tests no_root
}
function run_tests_no_pass() {
# Normal tests without password
run_tests no_pass
}
function run_tests_no_pass_altuid() {
# Test with arbitrary uid for the container without password
DOCKER_ARGS="-u 12345" run_tests no_pass_altuid
}
function run_tests_no_root_altuid() {
# Test with arbitrary uid for the container with password
DOCKER_ARGS="-u 12345" PASS=pass run_tests no_root_altuid
}
function run_all_tests() {
for test_case in $TEST_SET; do
echo "Running test $test_case ...."
TESTCASE_RESULT=0
$test_case
check_result $?
local test_msg
if [ $TESTCASE_RESULT -eq 0 ]; then
test_msg="[PASSED]"
else
test_msg="[FAILED]"
TESTSUITE_RESULT=1
fi
printf -v test_short_summary "%s %s for '%s' %s\n" "${test_short_summary}" "${test_msg}" "$test_case"
[ -n "${FAIL_QUICKLY:-}" ] && cleanup "${APP_NAME}" && return 1
done;
}
TEST_SET=${TESTS:-$TEST_LIST} run_all_tests
echo "Success!"
cleanup

1
test/run-openshift Symbolic link
View file

@ -0,0 +1 @@
run-openshift-local-cluster

119
test/run-openshift-local-cluster Executable file
View file

@ -0,0 +1,119 @@
#!/bin/bash
#
# Test the Redis 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-openshift.sh
source ${THISDIR}/test-lib-redis.sh
set -eo nounset
trap ct_os_cleanup EXIT SIGINT
ct_os_check_compulsory_vars
ct_os_enable_print_logs
function check_redis_os_service_connection() {
local util_image_name=$1 ; shift
local service_name=$1 ; shift
local pass=$1 ; shift
local timeout=${1:-60} ; shift || :
local pod_ip=$(ct_os_get_service_ip ${service_name})
: " Service ${service_name} check ..."
local cmd="timeout 15 redis-cli -h $pod_ip -a $pass ping"
local expected_value="PONG"
local output
local ret
SECONDS=0
echo -n "Waiting for ${service_name} service becoming ready ..."
while true ; do
output=$(docker run --rm ${util_image_name} bash -c "${cmd}" || :)
echo "${output}" | grep -qe "${expected_value}" && ret=0 || 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
}
function test_redis_pure_image() {
local image_name=$1
local image_name_no_namespace=${image_name##*/}
local service_name="${image_name_no_namespace%%:*}-testing"
ct_os_new_project
# Create a specific imagestream tag for the image so that oc cannot use anything else
ct_os_upload_image "${image_name}" "$image_name_no_namespace"
ct_os_deploy_pure_image "$image_name_no_namespace" \
--name "${service_name}" \
--env REDIS_PASSWORD=pass
ct_os_wait_pod_ready "${service_name}" 60
check_redis_os_service_connection "${image_name}" "${service_name}" pass
ct_os_delete_project
}
function test_redis_template() {
local image_name=${1:-quay.io/centos7/redis-5-centos7}
local image_name_no_namespace=${image_name##*/}
local service_name="${image_name_no_namespace%%:*}-testing"
ct_os_new_project
ct_os_upload_image "${image_name}" "redis:$VERSION"
ct_os_deploy_template_image "$THISDIR/redis-ephemeral-template.json" \
NAMESPACE="$(oc project -q)" \
REDIS_VERSION="$VERSION" \
DATABASE_SERVICE_NAME="${service_name}" \
REDIS_PASSWORD=pass
ct_os_wait_pod_ready "${service_name}" 60
check_redis_os_service_connection "${image_name}" "${service_name}" pass
ct_os_delete_project
}
ct_os_cluster_up
test_redis_pure_image "${IMAGE_NAME}"
test_redis_template "${IMAGE_NAME}"
# test with the just built image and an integrated template
test_redis_integration "${IMAGE_NAME}"
# test with a released image and an integrated template
PUBLIC_IMAGE_NAME=${PUBLIC_IMAGE_NAME:-$(ct_get_public_image_name "${OS}" "${BASE_IMAGE_NAME}" "${VERSION}")}
# Try pulling the image first to see if it is accessible
if ct_check_image_availability "$PUBLIC_IMAGE_NAME"; then
test_redis_integration "${PUBLIC_IMAGE_NAME}"
else
echo "Warning: ${PUBLIC_IMAGE_NAME} could not be downloaded via 'docker'"
# ignore possible failure of this test for centos images
[ "${OS}" == "rhel7" ] && false "ERROR: Failed to pull image"
fi
# Check the imagestream
test_redis_imagestream
OS_TESTSUITE_RESULT=0
ct_os_cluster_down
# vim: set tabstop=2:shiftwidth=2:expandtab:

View file

@ -0,0 +1,37 @@
#!/bin/bash
#
# Test the Redis image in OpenShift (remote cluster)
#
# IMAGE_NAME specifies a name of the candidate image used for testing.
# The image has to be available before this script is executed.
# VERSION specifies the major version of the Redis in format of X.Y
# OS specifies RHEL version (e.g. OS=rhel7)
#
THISDIR=$(dirname ${BASH_SOURCE[0]})
source ${THISDIR}/test-lib-redis.sh
set -eo nounset
trap ct_os_cleanup EXIT SIGINT
ct_os_check_compulsory_vars
oc status || false "It looks like oc is not properly logged in."
# For testing on OpenShift 4 we use external registry
export CT_EXTERNAL_REGISTRY=true
ct_os_set_ocp4
# Check the template
test_redis_integration "${IMAGE_NAME}"
# Check the imagestream
test_redis_imagestream
OS_TESTSUITE_RESULT=0
# vim: set tabstop=2:shiftwidth=2:expandtab:

1343
test/test-lib-openshift.sh Normal file

File diff suppressed because it is too large Load diff

37
test/test-lib-redis.sh Normal file
View file

@ -0,0 +1,37 @@
#!/bin/bash
#
# Functions for tests for the Redis 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
source ${THISDIR}/test-lib-remote-openshift.sh
function test_redis_integration() {
local image_name=$1
local service_name=redis
ct_os_test_template_app_func "${image_name}" \
"https://raw.githubusercontent.com/openshift/origin/master/examples/db-templates/redis-ephemeral-template.json" \
"${service_name}" \
"ct_os_check_cmd_internal '<SAME_IMAGE>' '${service_name}-testing' 'timeout 15 redis-cli -h <IP> -a testp ping' 'PONG'" \
"-p REDIS_VERSION=${VERSION} \
-p DATABASE_SERVICE_NAME="${service_name}-testing" \
-p REDIS_PASSWORD=testp"
}
# Check the imagestream
function test_redis_imagestream() {
case ${OS} in
rhel7|centos7) ;;
*) echo "Imagestream testing not supported for $OS environment." ; return 0 ;;
esac
ct_os_test_image_stream_template "${THISDIR}/../imagestreams/redis-${OS%[0-9]*}.json" "${THISDIR}/../examples/redis-ephemeral-template.json" redis "-p REDIS_VERSION=${VERSION}"
}
# vim: set tabstop=2:shiftwidth=2:expandtab:

View file

@ -0,0 +1,112 @@
# shellcheck shell=bash
# some functions are used from test-lib.sh, that is usually in the same dir
# shellcheck source=/dev/null
source "$(dirname "${BASH_SOURCE[0]}")"/test-lib.sh
# Set of functions for testing docker images in OpenShift using 'oc' command
# A variable containing the overall test result; must be changed to 0 in the end
# of the testing script:
# OS_TESTSUITE_RESULT=0
# And the following trap must be set, in the beginning of the test script:
# trap ct_os_cleanup EXIT SIGINT
# ct_os_set_path_oc_4 OC_VERSION
# --------------------
# This is a trick that helps using correct version 4 of the `oc`:
# The input is version of the openshift in format 4.4 etc.
# If the currently available version of oc is not of this version,
# it first takes a look into /usr/local/oc-<ver>/bin directory,
# Arguments: oc_version - X.Y part of the version of OSE (e.g. 3.9)
function ct_os_set_path_oc_4() {
echo "Setting OCP4 client"
local oc_version=$1
local installed_oc_path="/usr/local/oc-v${oc_version}/bin"
echo "PATH ${installed_oc_path}"
if [ -x "${installed_oc_path}/oc" ] ; then
oc_path="${installed_oc_path}"
echo "Binary oc found in ${installed_oc_path}" >&2
else
echo "OCP4 not found"
return 1
fi
export PATH="${oc_path}:${PATH}"
oc version
if ! oc version | grep -q "Client Version: ${oc_version}." ; then
echo "ERROR: something went wrong, oc located at ${oc_path}, but oc of version ${oc_version} not found in PATH ($PATH)" >&1
return 1
else
echo "PATH set correctly, binary oc found in version ${oc_version}: $(command -v oc)"
fi
}
# ct_os_prepare_ocp4
# ------------------
# Prepares environment for testing images in OpenShift 4 environment
#
#
function ct_os_set_ocp4() {
local login
OS_OC_CLIENT_VERSION=${OS_OC_CLIENT_VERSION:-4.4}
ct_os_set_path_oc_4 "${OS_OC_CLIENT_VERSION}"
oc version
login=$(cat "$KUBEPASSWORD")
oc login -u kubeadmin -p "$login"
echo "Login to OpenShift ${OS_OC_CLIENT_VERSION} is DONE"
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
function ct_os_upload_image_external_registry() {
local input_name="${1}" ; shift
local image_name=${input_name##*/}
local imagestream=${1:-$image_name:latest}
local output_name
ct_os_login_external_registry
output_name="${INTERNAL_DOCKER_REGISTRY}/rhscl-ci-testing/$imagestream"
docker images
docker tag "${input_name}" "${output_name}"
docker push "${output_name}"
}
function ct_os_login_external_registry() {
local docker_token
# docker login fails with "404 page not found" error sometimes, just try it more times
# shellcheck disable=SC2034
echo "loging"
[ -z "${INTERNAL_DOCKER_REGISTRY:-}" ] && "INTERNAL_DOCKER_REGISTRY has to be set for working with Internal registry" && return 1
# shellcheck disable=SC2034
for i in $(seq 12) ; do
# shellcheck disable=SC2015
docker_token=$(cat "$DOCKER_UPSHIFT_TOKEN")
# shellcheck disable=SC2015
docker login -u rhscl-ci-testing -p "$docker_token" "${INTERNAL_DOCKER_REGISTRY}" && return 0 || :
sleep 5
done
return 1
}
function ct_os_import_image_ocp4() {
local image_name="${1}"; shift
local imagestream=${1:-$image_name:latest}
local namespace
namespace=${CT_NAMESPACE:-"$(oc project -q)"}
deploy_image_name="${INTERNAL_DOCKER_REGISTRY}/rhscl-ci-testing/${imagestream}"
echo "Uploading image ${image_name} as ${deploy_image_name} , ${imagestream} into external registry."
ct_os_upload_image_external_registry "${image_name}" "${imagestream}"
if [ "${CT_TAG_IMAGE:-false}" == 'true' ]; then
echo "Tag ${deploy_image_name} to ${namespace}/${imagestream}"
oc tag --source=docker "${deploy_image_name}" "${namespace}/${imagestream}" --insecure=true --reference-policy=local
else
echo "Import image into OpenShift 4 environment ${namespace}/${imagestream} from ${deploy_image_name}"
oc import-image "${namespace}/${imagestream}" --from="${deploy_image_name}" --confirm --reference-policy=local
fi
}

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

@ -0,0 +1,918 @@
# shellcheck shell=bash
#
# Test a container image.
#
# Always use sourced from a specific container testfile
#
# reguires definition of CID_FILE_DIR
# CID_FILE_DIR=$(mktemp --suffix=<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() {
ct_show_resources
for cid_file in "$CID_FILE_DIR"/* ; do
[ -f "$cid_file" ] || continue
local container
container=$(cat "$cid_file")
: "Stopping and removing container $container..."
docker stop "$container"
exit_status=$(docker inspect -f '{{.State.ExitCode}}' "$container")
if [ "$exit_status" != "$EXPECTED_EXIT_CODE" ]; then
: "Dumping logs for $container"
docker logs "$container"
fi
docker rm -v "$container"
rm "$cid_file"
done
rmdir "$CID_FILE_DIR"
: "Done."
}
# ct_enable_cleanup
# --------------------
# Enables automatic container cleanup after tests.
function ct_enable_cleanup() {
trap ct_cleanup EXIT SIGINT
}
# ct_check_envs_set env_filter check_envs loop_envs [env_format]
# --------------------
# Compares values from one list of environment variable definitions against such list,
# checking if the values are present and have a specific format.
# Argument: env_filter - optional string passed to grep used for
# choosing which variables to filter out in env var lists.
# Argument: check_envs - list of env var definitions to check values against
# Argument: loop_envs - list of env var definitions to check values for
# Argument: env_format (optional) - format string for bash substring deletion used
# for checking whether the value is contained in check_envs.
# Defaults to: "*VALUE*", VALUE string gets replaced by actual value from loop_envs
function ct_check_envs_set {
local env_filter check_envs env_format
env_filter=$1; shift
check_envs=$1; shift
loop_envs=$1; shift
env_format=${1:-"*VALUE*"}
while read -r variable; do
[ -z "$variable" ] && continue
var_name=$(echo "$variable" | awk -F= '{ print $1 }')
stripped=$(echo "$variable" | awk -F= '{ print $2 }')
filtered_envs=$(echo "$check_envs" | grep "^$var_name=")
[ -z "$filtered_envs" ] && { echo "$var_name not found during \` docker exec\`"; return 1; }
old_IFS=$IFS
# For each such variable compare its content with the `docker exec` result, use `:` as delimiter
IFS=:
for value in $stripped; do
# If the falue checked does not go through env_filter we do not care about it
echo "$value" | grep -q "$env_filter" || continue
if [ -n "${filtered_envs##${env_format//VALUE/$value}}" ]; then
echo " Value $value is missing from variable $var_name"
echo "$filtered_envs"
IFS=$old_IFS
return 1
fi
done
IFS=$old_IFS
done <<< "$(echo "$loop_envs" | grep "$env_filter" | grep -v "^PWD=")"
}
# ct_get_cid [name]
# --------------------
# Prints container id from cid_file based on the name of the file.
# Argument: name - name of cid_file where the container id will be stored
# Uses: $CID_FILE_DIR - path to directory containing cid_files
function ct_get_cid() {
local name="$1" ; shift || return 1
cat "$CID_FILE_DIR/$name"
}
# ct_get_cip [id]
# --------------------
# Prints container ip address based on the container id.
# Argument: id - container id
function ct_get_cip() {
local id="$1" ; shift
docker inspect --format='{{.NetworkSettings.IPAddress}}' "$(ct_get_cid "$id")"
}
# ct_wait_for_cid [cid_file]
# --------------------
# Holds the execution until the cid_file is created. Usually run after container
# creation.
# Argument: cid_file - name of the cid_file that should be created
function ct_wait_for_cid() {
local cid_file=$1
local max_attempts=10
local sleep_time=1
local attempt=1
local result=1
while [ $attempt -le $max_attempts ]; do
[ -f "$cid_file" ] && [ -s "$cid_file" ] && return 0
: "Waiting for container start..."
attempt=$(( attempt + 1 ))
sleep $sleep_time
done
return 1
}
# ct_assert_container_creation_fails [container_args]
# --------------------
# The invocation of docker run should fail based on invalid container_args
# passed to the function. Returns 0 when container fails to start properly.
# Argument: container_args - all arguments are passed directly to dokcer run
# Uses: $CID_FILE_DIR - path to directory containing cid_files
function ct_assert_container_creation_fails() {
local ret=0
local max_attempts=10
local attempt=1
local cid_file=assert
set +e
local old_container_args="${CONTAINER_ARGS-}"
# we really work with CONTAINER_ARGS as with a string
# shellcheck disable=SC2124
CONTAINER_ARGS="$@"
if ct_create_container "$cid_file" ; then
local cid
cid=$(ct_get_cid "$cid_file")
while [ "$(docker inspect -f '{{.State.Running}}' "$cid")" == "true" ] ; do
sleep 2
attempt=$(( attempt + 1 ))
if [ "$attempt" -gt "$max_attempts" ]; then
docker stop "$cid"
ret=1
break
fi
done
exit_status=$(docker inspect -f '{{.State.ExitCode}}' "$cid")
if [ "$exit_status" == "0" ]; then
ret=1
fi
docker rm -v "$cid"
rm "$CID_FILE_DIR/$cid_file"
fi
[ -n "$old_container_args" ] && CONTAINER_ARGS="$old_container_args"
set -e
return "$ret"
}
# ct_create_container [name, command]
# --------------------
# Creates a container using the IMAGE_NAME and CONTAINER_ARGS variables. Also
# stores the container id to a cid_file located in the CID_FILE_DIR, and waits
# for the creation of the file.
# Argument: name - name of cid_file where the container id will be stored
# Argument: command - optional command to be executed in the container
# Uses: $CID_FILE_DIR - path to directory containing cid_files
# Uses: $CONTAINER_ARGS - optional arguments passed directly to docker run
# Uses: $IMAGE_NAME - name of the image being tested
function ct_create_container() {
local cid_file="$CID_FILE_DIR/$1" ; shift
# create container with a cidfile in a directory for cleanup
# shellcheck disable=SC2086
docker run --cidfile="$cid_file" -d ${CONTAINER_ARGS:-} "$IMAGE_NAME" "$@"
ct_wait_for_cid "$cid_file" || return 1
: "Created container $(cat "$cid_file")"
}
# ct_scl_usage_old [name, command, expected]
# --------------------
# Tests three ways of running the SCL, by looking for an expected string
# in the output of the command
# Argument: name - name of cid_file where the container id will be stored
# Argument: command - executed inside the container
# Argument: expected - string that is expected to be in the command output
# Uses: $CID_FILE_DIR - path to directory containing cid_files
# Uses: $IMAGE_NAME - name of the image being tested
function ct_scl_usage_old() {
local name="$1"
local command="$2"
local expected="$3"
local out=""
: " Testing the image SCL enable"
out=$(docker run --rm "${IMAGE_NAME}" /bin/bash -c "${command}")
if ! echo "${out}" | grep -q "${expected}"; then
echo "ERROR[/bin/bash -c \"${command}\"] Expected '${expected}', got '${out}'" >&2
return 1
fi
out=$(docker exec "$(ct_get_cid "$name")" /bin/bash -c "${command}" 2>&1)
if ! echo "${out}" | grep -q "${expected}"; then
echo "ERROR[exec /bin/bash -c \"${command}\"] Expected '${expected}', got '${out}'" >&2
return 1
fi
out=$(docker exec "$(ct_get_cid "$name")" /bin/sh -ic "${command}" 2>&1)
if ! echo "${out}" | grep -q "${expected}"; then
echo "ERROR[exec /bin/sh -ic \"${command}\"] Expected '${expected}', got '${out}'" >&2
return 1
fi
}
# ct_doc_content_old [strings]
# --------------------
# Looks for occurence of stirngs in the documentation files and checks
# the format of the files. Files examined: help.1
# Argument: strings - strings expected to appear in the documentation
# Uses: $IMAGE_NAME - name of the image being tested
function ct_doc_content_old() {
local tmpdir
tmpdir=$(mktemp -d)
local f
: " Testing documentation in the container image"
# Extract the help files from the container
# shellcheck disable=SC2043
for f in help.1 ; do
docker run --rm "${IMAGE_NAME}" /bin/bash -c "cat /${f}" >"${tmpdir}/$(basename "${f}")"
# Check whether the files contain some important information
for term in "$@" ; do
if ! grep -F -q -e "${term}" "${tmpdir}/$(basename "${f}")" ; then
echo "ERROR: File /${f} does not include '${term}'." >&2
return 1
fi
done
# Check whether the files use the correct format
for term in TH PP SH ; do
if ! grep -q "^\.${term}" "${tmpdir}/help.1" ; then
echo "ERROR: /help.1 is probably not in troff or groff format, since '${term}' is missing." >&2
return 1
fi
done
done
: " Success!"
}
# full_ca_file_path
# Return string for full path to CA file
function full_ca_file_path()
{
echo "/etc/pki/ca-trust/source/anchors/RH-IT-Root-CA.crt"
}
# ct_mount_ca_file
# ------------------
# Check if /etc/pki/certs/RH-IT-Root-CA.crt file exists
# return mount string for containers or empty string
function ct_mount_ca_file()
{
# mount CA file only if NPM_REGISTRY variable is present.
local mount_parameter=""
if [ -n "$NPM_REGISTRY" ] && [ -f "$(full_ca_file_path)" ]; then
mount_parameter="-v $(full_ca_file_path):$(full_ca_file_path):Z"
fi
echo "$mount_parameter"
}
# ct_build_s2i_npm_variables URL_TO_NPM_JS_SERVER
# ------------------------------------------
# Function returns -e NPM_MIRROR and -v MOUNT_POINT_FOR_CAFILE
# or empty string
function ct_build_s2i_npm_variables()
{
npm_variables=""
if [ -n "$NPM_REGISTRY" ] && [ -f "$(full_ca_file_path)" ]; then
npm_variables="-e NPM_MIRROR=$NPM_REGISTRY $(ct_mount_ca_file)"
fi
echo "$npm_variables"
}
# ct_npm_works
# --------------------
# Checks existance of the npm tool and runs it.
function ct_npm_works() {
local tmpdir
tmpdir=$(mktemp -d)
: " Testing npm in the container image"
local cid_file="${tmpdir}/cid"
if ! docker run --rm "${IMAGE_NAME}" /bin/bash -c "npm --version" >"${tmpdir}/version" ; then
echo "ERROR: 'npm --version' does not work inside the image ${IMAGE_NAME}." >&2
return 1
fi
# shellcheck disable=SC2046
docker run -d $(ct_mount_ca_file) --rm --cidfile="$cid_file" "${IMAGE_NAME}-testapp"
# Wait for the container to write it's CID file
ct_wait_for_cid "$cid_file" || return 1
if ! docker exec "$(cat "$cid_file")" /bin/bash -c "npm --verbose install jquery && test -f node_modules/jquery/src/jquery.js" >"${tmpdir}/jquery" 2>&1 ; then
echo "ERROR: npm could not install jquery inside the image ${IMAGE_NAME}." >&2
return 1
fi
if [ -n "$NPM_REGISTRY" ] && [ -f "$(full_ca_file_path)" ]; then
if ! grep -qo "$NPM_REGISTRY" "${tmpdir}/jquery"; then
echo "ERROR: Internal repository is NOT set. Even it is requested."
return 1
fi
fi
if [ -f "$cid_file" ]; then
docker stop "$(cat "$cid_file")"
rm "$cid_file"
fi
: " Success!"
}
# ct_binary_found_from_df binary [path]
# --------------------
# Checks if a binary can be found in PATH during Dockerfile build
# Argument: binary - name of the binary to test accessibility for
# Argument: path - optional path in which the binary should reside in
# /opt/rh by default
function ct_binary_found_from_df() {
local tmpdir
local binary=$1; shift
local binary_path=${1:-"^/opt/rh"}
tmpdir=$(mktemp -d)
: " Testing $binary in build from Dockerfile"
# Create Dockerfile that looks for the binary
cat <<EOF >"$tmpdir/Dockerfile"
FROM $IMAGE_NAME
RUN command -v $binary | grep "$binary_path"
EOF
# Build an image, looking for expected path in the output
if ! docker build -f "$tmpdir/Dockerfile" --no-cache "$tmpdir"; then
echo " ERROR: Failed to find $binary in Dockerfile!" >&2
return 1
fi
: " Success!"
}
# ct_check_exec_env_vars [env_filter]
# --------------------
# Checks if all relevant environment variables from `docker run`
# can be found in `docker exec` as well.
# Argument: env_filter - optional string passed to grep used for
# choosing which variables to check in the test case.
# Defaults to X_SCLS and variables containing /opt/app-root, /opt/rh
# Uses: $CID_FILE_DIR - path to directory containing cid_files
# Uses: $IMAGE_NAME - name of the image being tested
function ct_check_exec_env_vars() {
local tmpdir exec_envs cid old_IFS env_filter
local var_name stripped filtered_envs run_envs
env_filter=${1:-"^X_SCLS=\|/opt/rh\|/opt/app-root"}
tmpdir=$(mktemp -d)
CID_FILE_DIR=${CID_FILE_DIR:-$(mktemp -d)}
# Get environment variables from `docker run`
run_envs=$(docker run --rm "$IMAGE_NAME" /bin/bash -c "env")
# Get environment variables from `docker exec`
ct_create_container "test_exec_envs" bash -c "sleep 1000" >/dev/null
cid=$(ct_get_cid "test_exec_envs")
exec_envs=$(docker exec "$cid" env)
# Filter out variables we are not interested in
# Always check X_SCLS, ignore PWD
# Check variables from `docker run` that have alternative paths inside (/opt/rh, /opt/app-root)
ct_check_envs_set "$env_filter" "$exec_envs" "$run_envs" "*VALUE*" || return 1
echo " All values present in \`docker exec\`"
return 0
}
# ct_check_scl_enable_vars [env_filter]
# --------------------
# Checks if all relevant environment variables from `docker run`
# are set twice after a second call of `scl enable $SCLS`.
# Argument: env_filter - optional string passed to grep used for
# choosing which variables to check in the test case.
# Defaults to paths containing enabled SCLS in the image
# Uses: $IMAGE_NAME - name of the image being tested
function ct_check_scl_enable_vars() {
local tmpdir exec_envs cid old_IFS env_filter enabled_scls
local var_name stripped filtered_envs loop_envs
env_filter=$1
tmpdir=$(mktemp -d)
enabled_scls=$(docker run --rm "$IMAGE_NAME" /bin/bash -c "echo \$X_SCLS")
if [ -z "$env_filter" ]; then
for scl in $enabled_scls; do
[ -z "$env_filter" ] && env_filter="/$scl" && continue
# env_filter not empty, append to the existing list
env_filter="$env_filter|/$scl"
done
fi
# Get environment variables from `docker run`
loop_envs=$(docker run --rm "$IMAGE_NAME" /bin/bash -c "env")
run_envs=$(docker run --rm "$IMAGE_NAME" /bin/bash -c "X_SCLS= scl enable $enabled_scls env")
# Check if the values are set twice in the second set of envs
ct_check_envs_set "$env_filter" "$run_envs" "$loop_envs" "*VALUE*VALUE*" || return 1
echo " All scl_enable values present"
return 0
}
# ct_path_append PATH_VARNAME DIRECTORY
# -------------------------------------
# Append DIRECTORY to VARIABLE of name PATH_VARNAME, the VARIABLE must consist
# of colon-separated list of directories.
ct_path_append ()
{
if eval "test -n \"\${$1-}\""; then
eval "$1=\$2:\$$1"
else
eval "$1=\$2"
fi
}
# ct_path_foreach PATH ACTION [ARGS ...]
# --------------------------------------
# For each DIR in PATH execute ACTION (path is colon separated list of
# directories). The particular calls to ACTION will look like
# '$ ACTION directory [ARGS ...]'
ct_path_foreach ()
{
local dir dirlist action save_IFS
save_IFS=$IFS
IFS=:
dirlist=$1
action=$2
shift 2
for dir in $dirlist; do "$action" "$dir" "$@" ; done
IFS=$save_IFS
}
# ct_run_test_list
# --------------------
# Execute the tests specified by TEST_LIST
# Uses: $TEST_LIST - list of test names
function ct_run_test_list() {
for test_case in $TEST_LIST; do
: "Running test $test_case"
# shellcheck source=/dev/null
[ -f "test/$test_case" ] && source "test/$test_case"
# shellcheck source=/dev/null
[ -f "../test/$test_case" ] && source "../test/$test_case"
$test_case
done;
}
# ct_gen_self_signed_cert_pem
# ---------------------------
# Generates a self-signed PEM certificate pair into specified directory.
# Argument: output_dir - output directory path
# Argument: base_name - base name of the certificate files
# Resulted files will be those:
# <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
output=$(mktemp "/var/tmp/test-input-XXXXXX$extension")
if [ -f "${input}" ] ; then
cp -f "${input}" "${output}"
elif [ -d "${input}" ] ; then
rm -f "${output}"
cp -r -LH "${input}" "${output}"
elif echo "${input}" | grep -qe '^http\(s\)\?://' ; then
curl "${input}" > "${output}"
else
echo "ERROR: file type not known: ${input}" >&2
return 1
fi
echo "${output}"
}
# ct_test_response
# ----------------
# Perform GET request to the application container, checks output with
# a reg-exp and HTTP response code.
# Argument: url - request URL path
# Argument: expected_code - expected HTTP response code
# Argument: body_regexp - PCRE regular expression that must match the response body
# Argument: max_attempts - Optional number of attempts (default: 20), three seconds sleep between
# Argument: ignore_error_attempts - Optional number of attempts when we ignore error output (default: 10)
ct_test_response() {
local url="$1"
local expected_code="$2"
local body_regexp="$3"
local max_attempts=${4:-20}
local ignore_error_attempts=${5:-10}
: " Testing the HTTP(S) response for <${url}>"
local sleep_time=3
local attempt=1
local result=1
local status
local response_code
local response_file
response_file=$(mktemp /tmp/ct_test_response_XXXXXX)
while [ "${attempt}" -le "${max_attempts}" ]; do
curl --connect-timeout 10 -s -w '%{http_code}' "${url}" >"${response_file}" && status=0 || status=1
if [ "${status}" -eq 0 ]; then
response_code=$(tail -c 3 "${response_file}")
if [ "${response_code}" -eq "${expected_code}" ]; then
result=0
fi
grep -qP -e "${body_regexp}" "${response_file}" || result=1;
# Some services return 40x code until they are ready, so let's give them
# some chance and not end with failure right away
# Do not wait if we already have expected outcome though
if [ "${result}" -eq 0 ] || [ "${attempt}" -gt "${ignore_error_attempts}" ] || [ "${attempt}" -eq "${max_attempts}" ] ; then
break
fi
fi
attempt=$(( attempt + 1 ))
sleep "${sleep_time}"
done
rm -f "${response_file}"
return "${result}"
}
# ct_registry_from_os OS
# ----------------
# Transform operating system string [os] into registry url
# Argument: OS - string containing the os version
ct_registry_from_os() {
local registry=""
case $1 in
rhel*)
registry=registry.redhat.io
;;
*)
registry=quay.io
;;
esac
echo "$registry"
}
# ct_get_public_image_name OS BASE_IMAGE_NAME VERSION
# ----------------
# Transform the arguments into public image name
# Argument: OS - string containing the os version
# Argument: BASE_IMAGE_NAME - string containing the base name of the image as defined in the Makefile
# Argument: VERSION - string containing the version of the image as defined in the Makefile
ct_get_public_image_name() {
local os=$1; shift
local base_image_name=$1; shift
local version=$1; shift
local public_image_name
local registry
registry=$(ct_registry_from_os "$os")
if [ "x$os" == "xrhel7" ]; then
public_image_name=$registry/rhscl/$base_image_name-${version//./}-rhel7
elif [ "x$os" == "xrhel8" ]; then
public_image_name=$registry/rhel8/$base_image_name-${version//./}
elif [ "x$os" == "xcentos7" ]; then
public_image_name=$registry/centos7/$base_image_name-${version//./}-centos7
elif [ "x$os" == "xcentos8" ]; then
public_image_name=$registry/centos8/$base_image_name-${version//./}-centos8
fi
echo "$public_image_name"
}
# ct_assert_cmd_success CMD
# ----------------
# Evaluates [cmd] and fails if it does not succeed.
# Argument: CMD - Command to be run
function ct_assert_cmd_success() {
echo "Checking '$*' for success ..."
if ! eval "$@" &>/dev/null; then
echo " FAIL"
return 1
fi
echo " PASS"
return 0
}
# ct_assert_cmd_failure CMD
# ----------------
# Evaluates [cmd] and fails if it succeeds.
# Argument: CMD - Command to be run
function ct_assert_cmd_failure() {
echo "Checking '$*' for failure ..."
if eval "$@" &>/dev/null; then
echo " FAIL"
return 1
fi
echo " PASS"
return 0
}
# ct_random_string [LENGTH=10]
# ----------------------------
# Generate pseudorandom alphanumeric string of LENGTH bytes, the
# default length is 10. The string is printed on stdout.
ct_random_string()
(
export LC_ALL=C
dd if=/dev/urandom count=1 bs=10k 2>/dev/null \
| tr -dc 'a-z0-9' \
| fold -w "${1-10}" \
| head -n 1
)
# ct_s2i_usage IMG_NAME [S2I_ARGS]
# ----------------------------
# Create a container and run the usage script inside
# Argument: IMG_NAME - name of the image to be used for the container run
# Argument: S2I_ARGS - Additional list of source-to-image arguments, currently unused.
ct_s2i_usage()
{
local img_name=$1; shift
local s2i_args="$*";
local usage_command="/usr/libexec/s2i/usage"
docker run --rm "$img_name" bash -c "$usage_command"
}
# ct_s2i_build_as_df APP_PATH SRC_IMAGE DST_IMAGE [S2I_ARGS]
# ----------------------------
# Create a new s2i app image from local sources in a similar way as source-to-image would have used.
# Argument: APP_PATH - local path to the app sources to be used in the test
# Argument: SRC_IMAGE - image to be used as a base for the s2i build
# Argument: DST_IMAGE - image name to be used during the tagging of the s2i build result
# Argument: S2I_ARGS - Additional list of source-to-image arguments.
# Only used to check for pull-policy=never and environment variable definitions.
ct_s2i_build_as_df()
{
local app_path=$1; shift
local src_image=$1; shift
local dst_image=$1; shift
local s2i_args="$*";
local local_app=upload/src/
local local_scripts=upload/scripts/
local user_id=
local df_name=
local tmpdir=
local incremental=false
local mount_options=""
# Run the entire thing inside a subshell so that we do not leak shell options outside of the function
(
# Error out if any part of the build fails
set -e
# Use /tmp to not pollute cwd
tmpdir=$(mktemp -d)
df_name=$(mktemp -p "$tmpdir" Dockerfile.XXXX)
cd "$tmpdir"
# Check if the image is available locally and try to pull it if it is not
docker images "$src_image" &>/dev/null || echo "$s2i_args" | grep -q "pull-policy=never" || docker pull "$src_image"
user=$(docker inspect -f "{{.Config.User}}" "$src_image")
# Default to root if no user is set by the image
user=${user:-0}
# run the user through the image in case it is non-numeric or does not exist
# NOTE: The '-eq' test is used to check if $user is numeric as it will fail if $user is not an integer
if ! [ "$user" -eq "$user" ] 2>/dev/null && ! user_id=$(docker run --rm "$src_image" bash -c "id -u $user 2>/dev/null"); then
echo "ERROR: id of user $user not found inside image $src_image."
echo "Terminating s2i build."
return 1
else
user_id=${user_id:-$user}
fi
echo "$s2i_args" | grep -q "\-\-incremental" && incremental=true
if $incremental; then
inc_tmp=$(mktemp -d --tmpdir incremental.XXXX)
setfacl -m "u:$user_id:rwx" "$inc_tmp"
# Check if the image exists, build should fail (for testing use case) if it does not
docker images "$dst_image" &>/dev/null || (echo "Image $dst_image not found."; false)
# Run the original image with a mounted in volume and get the artifacts out of it
cmd="if [ -s /usr/libexec/s2i/save-artifacts ]; then /usr/libexec/s2i/save-artifacts > \"$inc_tmp/artifacts.tar\"; else touch \"$inc_tmp/artifacts.tar\"; fi"
docker run --rm -v "$inc_tmp:$inc_tmp:Z" "$dst_image" bash -c "$cmd"
# Move the created content into the $tmpdir for the build to pick it up
mv "$inc_tmp/artifacts.tar" "$tmpdir/"
fi
# Strip file:// from APP_PATH and copy its contents into current context
mkdir -p "$local_app"
cp -r "${app_path/file:\/\//}/." "$local_app"
[ -d "$local_app/.s2i/bin/" ] && mv "$local_app/.s2i/bin" "$local_scripts"
# Create a Dockerfile named df_name and fill it with proper content
#FIXME: Some commands could be combined into a single layer but not sure if worth the trouble for testing purposes
cat <<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"
# Check if CA autority is present on host and add it into Dockerfile
[ -f "$(full_ca_file_path)" ] && echo "RUN cd /etc/pki/ca-trust/source/anchors && update-ca-trust extract" >>"$df_name"
# Add in artifacts if doing an incremental build
if $incremental; then
{ echo "RUN mkdir /tmp/artifacts"
echo "ADD artifacts.tar /tmp/artifacts"
echo "RUN chown -R $user_id:0 /tmp/artifacts" ; } >>"$df_name"
fi
echo "USER $user_id" >>"$df_name"
# If exists, run the custom assemble script, else default to /usr/libexec/s2i/assemble
if [ -x "$local_scripts/assemble" ]; then
echo "RUN /tmp/scripts/assemble" >>"$df_name"
else
echo "RUN /usr/libexec/s2i/assemble" >>"$df_name"
fi
# If exists, set the custom run script as CMD, else default to /usr/libexec/s2i/run
if [ -x "$local_scripts/run" ]; then
echo "CMD /tmp/scripts/run" >>"$df_name"
else
echo "CMD /usr/libexec/s2i/run" >>"$df_name"
fi
# Check if -v parameter is present in s2i_args and add it into docker build command
mount_options=$(echo "$s2i_args" | grep -o -e '\(-v\)[[:space:]]\.*\S*' || true)
# Run the build and tag the result
# shellcheck disable=SC2086
docker build $mount_options -f "$df_name" --no-cache=true -t "$dst_image" .
)
}
# ct_check_image_availability PUBLIC_IMAGE_NAME
# ----------------------------
# Pull an image from the public repositories to see if the image is already available.
# Argument: PUBLIC_IMAGE_NAME - string containing the public name of the image to pull
ct_check_image_availability() {
local public_image_name=$1;
# Try pulling the image to see if it is accessible
if ! docker pull "$public_image_name" &>/dev/null; then
echo "$public_image_name could not be downloaded via 'docker'"
return 1
fi
}
# ct_check_latest_imagestreams
# -----------------------------
# Check if the latest version present in Makefile in the variable VERSIONS
# is present in all imagestreams.
# Also the latest tag in the imagestreams has to contain the latest version
ct_check_latest_imagestreams() {
local latest_version=
local test_lib_dir=
# We only maintain imagestreams for RHEL and CentOS (Community)
if [[ "$OS" =~ ^fedora.* ]] ; then
echo "Imagestreams for Fedora are not maintained, skipping ct_check_latest_imagestreams"
return 0
fi
# Check only lines which starts with VERSIONS
latest_version=$(grep '^VERSIONS' Makefile | rev | cut -d ' ' -f 1 | rev )
# Fall back to previous version if the latest is excluded for this OS
[ -f "$latest_version/.exclude-$OS" ] && latest_version=$(grep '^VERSIONS' Makefile | rev | cut -d ' ' -f 2 | rev )
# Only test the imagestream once, when the version matches
# ignore the SC warning, $VERSION is always available
# shellcheck disable=SC2153
if [ "$latest_version" == "$VERSION" ]; then
test_lib_dir=$(dirname "$(readlink -f "$0")")
python3 "${test_lib_dir}/check_imagestreams.py" "$latest_version"
else
echo "Image version $VERSION is not latest, skipping ct_check_latest_imagestreams"
fi
}
# ct_show_resources
# ----------------
# Prints the available resources
ct_show_resources()
{
echo "Resources info:"
echo "Memory:"
free -h
echo "Storage:"
df -h || :
echo "CPU"
lscpu
}
# ct_test_app_dockerfile
# -----------------------------
# Argument: dockerfile - path to a Dockerfile that will be used for building an image
# (must work with an application directory called 'app-src')
# Argument: app_url - git or local URI with a testing application, supports "@" to indicate a different branch
# Argument: body_regexp - PCRE regular expression that must match the response body
# Argument: app_dir - name of the application directory that is used in the Dockerfile
# Argument: port - Optional port number (default: 8080)
ct_test_app_dockerfile() {
local dockerfile=$1
local app_url=$2
local expected_text=$3
local app_dir=$4 # this is a directory that must match with the name in the Dockerfile
local port=${5:-8080}
local app_image_name=myapp
local ret
local cname=app_dockerfile
if [ -z "$app_dir" ] ; then
echo "ERROR: Option app_dir not set. Terminating the Dockerfile build."
return 1
fi
if ! [ -r "${dockerfile}" ] || ! [ -s "${dockerfile}" ] ; then
echo "ERROR: Dockerfile ${dockerfile} does not exist or is empty."
echo "Terminating the Dockerfile build."
return 1
fi
CID_FILE_DIR=${CID_FILE_DIR:-$(mktemp -d)}
local dockerfile_abs
dockerfile_abs=$(readlink -f "${dockerfile}")
tmpdir=$(mktemp -d)
pushd "$tmpdir" >/dev/null
cp "${dockerfile_abs}" Dockerfile
# Rewrite the source image to what we test
sed -i -e "s|^FROM.*$|FROM $IMAGE_NAME|" Dockerfile
# a bit more verbose, but should help debugging failures
echo "Using this Dockerfile:"
cat Dockerfile
if [ -d "$app_url" ] ; then
echo "Copying local folder: $app_url -> $app_dir."
cp -Lr $app_url $app_dir
else
# If app_url contains @, the string after @ is considered
# as a name of a branch to clone instead of the main/master branch
IFS='@' read -ra git_url_parts <<< "${app_url}"
if [ -n "${git_url_parts[1]}" ]; then
git_clone_cmd="git clone --branch ${git_url_parts[1]} ${git_url_parts[0]} ${app_dir}"
else
git_clone_cmd="git clone ${app_url} ${app_dir}"
fi
if ! $git_clone_cmd ; then
echo "ERROR: Git repository ${app_url} cannot be cloned into ${app_dir}."
echo "Terminating the Dockerfile build."
return 1
fi
fi
echo "Building '${app_image_name}' image using docker build"
if ! docker build --no-cache=true -t "${app_image_name}" . ; then
echo "ERROR: The image cannot be built from ${dockerfile} and application ${app_url}."
echo "Terminating the Dockerfile build."
return 1
fi
if ! docker run -d --cidfile="${CID_FILE_DIR}/app_dockerfile" --rm "${app_image_name}" ; then
echo "ERROR: The image ${app_image_name} cannot be run for ${dockerfile} and application ${app_url}."
echo "Terminating the Dockerfile build."
return 1
fi
echo "Waiting for ${app_image_name} to start"
ct_wait_for_cid "${CID_FILE_DIR}/app_dockerfile"
ip="$(ct_get_cip "${cname}")"
ct_test_response "http://$ip:${port}" 200 "${expected_text}"
ret=$?
# cleanup
docker kill "$(ct_get_cid "${cname}")"
sleep 2
docker rmi "${app_image_name}"
popd >/dev/null
rm -rf "${tmpdir}"
rm -f "${CID_FILE_DIR}/${cname}"
return $ret
}
# vim: set tabstop=2:shiftwidth=2:expandtab: