From c8a1e3c83480c7c7f9602b2433c05c66324d9486 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Wed, 3 Jan 2024 18:06:46 +0100 Subject: [PATCH] Created Docker image for 2.5.0 --- 2.5.0/Dockerfile | 67 +++++++++ 2.5.0/build_deps.txt | 14 ++ 2.5.0/buildout.cfg | 57 ++++++++ 2.5.0/docker-compose.yml | 41 ++++++ 2.5.0/docker-entrypoint.sh | 55 ++++++++ 2.5.0/docker-initialize.py | 282 +++++++++++++++++++++++++++++++++++++ 2.5.0/requirements.txt | 1 + 2.5.0/run_deps.txt | 14 ++ latest/Dockerfile | 4 +- latest/requirements.txt | 2 +- stack.yml | 2 +- 11 files changed, 535 insertions(+), 4 deletions(-) create mode 100644 2.5.0/Dockerfile create mode 100644 2.5.0/build_deps.txt create mode 100644 2.5.0/buildout.cfg create mode 100644 2.5.0/docker-compose.yml create mode 100755 2.5.0/docker-entrypoint.sh create mode 100755 2.5.0/docker-initialize.py create mode 100644 2.5.0/requirements.txt create mode 100644 2.5.0/run_deps.txt diff --git a/2.5.0/Dockerfile b/2.5.0/Dockerfile new file mode 100644 index 0000000..3ec83ea --- /dev/null +++ b/2.5.0/Dockerfile @@ -0,0 +1,67 @@ +# Use an official Python runtime as a parent image +FROM python:2.7-slim-buster + +# Set one or more individual labels +LABEL maintainer="Ramon Bartl" +LABEL email="rb@ridingbytes.com" +LABEL senaite.core.version="v2.5.0" + +# Set environment variables +ENV PLONE_MAJOR=5.2 \ + PLONE_VERSION=5.2.14 \ + PLONE_MD5=e8e1f774f069026319be3038631e0734 \ + PLONE_UNIFIED_INSTALLER=Plone-5.2.14-UnifiedInstaller-1.0 \ + SENAITE_HOME=/home/senaite \ + SENAITE_USER=senaite \ + SENAITE_INSTANCE_HOME=/home/senaite/senaitelims \ + SENAITE_DATA=/data \ + SENAITE_FILESTORAGE=/data/filestorage \ + SENAITE_BLOBSTORAGE=/data/blobstorage + +# Create the senaite user +RUN useradd --system -m -d $SENAITE_HOME -U -u 500 $SENAITE_USER + +# Create directories +RUN mkdir -p $SENAITE_INSTANCE_HOME $SENAITE_FILESTORAGE $SENAITE_BLOBSTORAGE + +# Copy Buildout +COPY requirements.txt buildout.cfg $SENAITE_INSTANCE_HOME/ + +# Copy the build dependencies and startup scripts +COPY build_deps.txt run_deps.txt docker-initialize.py docker-entrypoint.sh / + +# Note: we concatenate all commands to avoid multiple layer generation and reduce the image size +RUN apt-get update \ + # Install system pakages + && apt-get install -y --no-install-recommends $(grep -vE "^\s*#" /build_deps.txt | tr "\n" " ") \ + && apt-get install -y --no-install-recommends $(grep -vE "^\s*#" /run_deps.txt | tr "\n" " ") \ + # Fetch unified installer + && wget -O Plone.tgz https://launchpad.net/plone/$PLONE_MAJOR/$PLONE_VERSION/+download/$PLONE_UNIFIED_INSTALLER.tgz \ + && echo "$PLONE_MD5 Plone.tgz" | md5sum -c - \ + && tar -xzf /Plone.tgz \ + && cp -rv $PLONE_UNIFIED_INSTALLER/base_skeleton/* $SENAITE_INSTANCE_HOME \ + && cp -v $PLONE_UNIFIED_INSTALLER/buildout_templates/buildout.cfg $SENAITE_INSTANCE_HOME/buildout-base.cfg \ + && rm -rf $PLONE_UNIFIED_INSTALLER Plone.tgz \ + # Buildout + && cd $SENAITE_INSTANCE_HOME \ + && pip install -r requirements.txt \ + && buildout \ + && ln -s $SENAITE_FILESTORAGE/ var/filestorage \ + && ln -s $SENAITE_BLOBSTORAGE/ var/blobstorage \ + && chown -R senaite:senaite $SENAITE_HOME $SENAITE_DATA \ + # Cleanup + && apt-get purge -y --auto-remove $(grep -vE "^\s*#" /build_deps.txt | tr "\n" " ") \ + && rm -rf /$SENAITE_HOME/buildout-cache \ + && rm -rf /var/lib/apt/lists/* + +# Change working directory +WORKDIR $SENAITE_INSTANCE_HOME + +# Mount external volume +VOLUME /data + +# Expose instance port +EXPOSE 8080 + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["start"] diff --git a/2.5.0/build_deps.txt b/2.5.0/build_deps.txt new file mode 100644 index 0000000..1a8cec1 --- /dev/null +++ b/2.5.0/build_deps.txt @@ -0,0 +1,14 @@ +dpkg-dev +gcc +libbz2-dev +libc6-dev +libffi-dev +libjpeg62-turbo-dev +libopenjp2-7-dev +libpcre3-dev +libssl-dev +libtiff5-dev +libxml2-dev +libxslt1-dev +wget +zlib1g-dev diff --git a/2.5.0/buildout.cfg b/2.5.0/buildout.cfg new file mode 100644 index 0000000..b0cfee8 --- /dev/null +++ b/2.5.0/buildout.cfg @@ -0,0 +1,57 @@ +[buildout] +index = https://pypi.python.org/simple/ +extends = + buildout-base.cfg +extensions = mr.developer + +var-dir=/data +user=admin:admin + +effective-user = senaite +buildout-user = senaite + +eggs-directory=eggs +download-cache=../buildout-cache/downloads + +parts += + zeo + plonesite + console_scripts + +eggs += + senaite.lims + +[client1] +recipe = + +[zeo] +<= zeoserver_base +recipe = plone.recipe.zeoserver +zeo-address = 8080 + +[instance] +# taken from https://github.com/plone/plone.docker/blob/master/5.2/5.2.5/python2/buildout.cfg +event-log-handler = StreamHandler +event-log-args = (sys.stderr,) +access-log-handler = StreamHandler +access-log-args = (sys.stdout,) + +[console_scripts] +recipe = zc.recipe.egg:scripts +eggs = senaite.core + +[plonesite] +recipe = collective.recipe.plonesite +instance = instance +site-id = senaite +profiles-initial = Products.CMFPlone:dependencies +profiles = + senaite.lims:default +upgrade-portal = False +upgrade-all-profiles = False +enabled = False + +[versions] +setuptools = +zc.buildout = +senaite.lims = 2.5.0 diff --git a/2.5.0/docker-compose.yml b/2.5.0/docker-compose.yml new file mode 100644 index 0000000..783ffe1 --- /dev/null +++ b/2.5.0/docker-compose.yml @@ -0,0 +1,41 @@ +version: "3" + +services: + + zeo: + image: senaite/senaite:v2.5.0 + command: zeo + volumes: + - ./data:/data + + instance1: + image: senaite/senaite:v2.5.0 + ports: + - 8081:8080 + links: + - zeo + deploy: + resources: + limits: + cpus: "1" + memory: 2048MB + environment: + ZEO_ADDRESS: "zeo:8080" + + instance2: + image: senaite/senaite:v2.5.0 + ports: + - 8082:8080 + links: + - zeo + deploy: + resources: + limits: + cpus: "1" + memory: 2048MB + environment: + ZEO_ADDRESS: "zeo:8080" + +volumes: + data: + external: true diff --git a/2.5.0/docker-entrypoint.sh b/2.5.0/docker-entrypoint.sh new file mode 100755 index 0000000..6c60e0c --- /dev/null +++ b/2.5.0/docker-entrypoint.sh @@ -0,0 +1,55 @@ +#!/bin/bash +set -e + +COMMANDS="adduser debug fg foreground help kill logreopen logtail reopen_transcript run show status stop wait" +START="console start restart" + +# Fixing permissions for external /data volumes +mkdir -p /data/blobstorage /data/cache /data/filestorage /data/instance /data/log /data/zeoserver +mkdir -p /home/senaite/senaitelims/src +find /data -not -user senaite -exec chown senaite:senaite {} \+ +find /home/senaite -not -user senaite -exec chown senaite:senaite {} \+ + + +# Initializing from environment variables +gosu senaite python /docker-initialize.py + +function git_fixture { + for d in `find /home/senaite/senaitelims/src -mindepth 1 -maxdepth 1 -type d` + do + if [ -d "$d/.git" ]; then + git config --global --add safe.directory $d + echo "git config --global --add safe.directory $d" + fi + done +} + +# Fix mr.developer: fatal: detected dubious ownership in repository at ... +# https://github.com/actions/runner-images/issues/6775 +# https://github.com/senaite/senaite.docker/issues/17 +git_fixture + +if [ -e "custom.cfg" ]; then + buildout -c custom.cfg + find /data -not -user senaite -exec chown senaite:senaite {} \+ + find /home/senaite -not -user senaite -exec chown senaite:senaite {} \+ + gosu senaite python /docker-initialize.py +fi + +# ZEO Server +if [[ "$1" == "zeo"* ]]; then + exec gosu senaite bin/$1 fg +fi + +# Instance start +if [[ $START == *"$1"* ]]; then + exec gosu senaite bin/instance console +fi + +# Instance helpers +if [[ $COMMANDS == *"$1"* ]]; then + exec gosu senaite bin/instance "$@" +fi + +# Custom +exec "$@" diff --git a/2.5.0/docker-initialize.py b/2.5.0/docker-initialize.py new file mode 100755 index 0000000..7089535 --- /dev/null +++ b/2.5.0/docker-initialize.py @@ -0,0 +1,282 @@ +#!/usr/local/bin/python + +import re +import os + + +class Environment(object): + """ Configure container via environment variables + """ + def __init__( + self, env=os.environ, + zope_conf="/home/senaite/senaitelims/parts/instance/etc/zope.conf", + custom_conf="/home/senaite/senaitelims/custom.cfg", + zeopack_conf="/home/senaite/senaitelims/bin/zeopack", + zeoserver_conf="/home/senaite/senaitelims/parts/zeoserver/etc/zeo.conf", + cors_conf="/home/senaite/senaitelims/parts/instance/etc/package-includes/999-additional-overrides.zcml" + ): + self.env = env + self.zope_conf = zope_conf + self.custom_conf = custom_conf + self.zeopack_conf = zeopack_conf + self.zeoserver_conf = zeoserver_conf + self.cors_conf = cors_conf + + def zeoclient(self): + """ ZEO Client + """ + server = self.env.get("ZEO_ADDRESS", None) + if not server: + return + + config = "" + with open(self.zope_conf, "r") as cfile: + config = cfile.read() + + # Already initialized + if "" not in config: + return + + read_only = self.env.get("ZEO_READ_ONLY", "false") + zeo_ro_fallback = self.env.get("ZEO_CLIENT_READ_ONLY_FALLBACK", "false") + shared_blob_dir = self.env.get("ZEO_SHARED_BLOB_DIR", "off") + zeo_storage = self.env.get("ZEO_STORAGE", "1") + zeo_client_cache_size = self.env.get("ZEO_CLIENT_CACHE_SIZE", "128MB") + zeo_conf = ZEO_TEMPLATE.format( + zeo_address=server, + read_only=read_only, + zeo_client_read_only_fallback=zeo_ro_fallback, + shared_blob_dir=shared_blob_dir, + zeo_storage=zeo_storage, + zeo_client_cache_size=zeo_client_cache_size + ) + + pattern = re.compile(r".+", re.DOTALL) + config = re.sub(pattern, zeo_conf, config) + + with open(self.zope_conf, "w") as cfile: + cfile.write(config) + + def zeopack(self): + """ ZEO Pack + """ + server = self.env.get("ZEO_ADDRESS", None) + if not server: + return + + if ":" in server: + host, port = server.split(":") + else: + host, port = (server, "8100") + + with open(self.zeopack_conf, 'r') as cfile: + text = cfile.read() + text = text.replace('address = "8100"', 'address = "%s"' % server) + text = text.replace('host = "127.0.0.1"', 'host = "%s"' % host) + text = text.replace('port = "8100"', 'port = "%s"' % port) + + with open(self.zeopack_conf, 'w') as cfile: + cfile.write(text) + + def zeoserver(self): + """ ZEO Server + """ + pack_keep_old = self.env.get("ZEO_PACK_KEEP_OLD", '') + if pack_keep_old.lower() in ("false", "no", "0", "n", "f"): + with open(self.zeoserver_conf, 'r') as cfile: + text = cfile.read() + if 'pack-keep-old' not in text: + text = text.replace( + '', + ' pack-keep-old false\n' + ) + + with open(self.zeoserver_conf, 'w') as cfile: + cfile.write(text) + + def cors(self): + """ Configure CORS Policies + """ + if not [e for e in self.env if e.startswith("CORS_")]: + return + + allow_origin = self.env.get( + "CORS_ALLOW_ORIGIN", + "http://localhost:3000,http://127.0.0.1:3000") + allow_methods = self.env.get( + "CORS_ALLOW_METHODS", + "DELETE,GET,OPTIONS,PATCH,POST,PUT") + allow_credentials = self.env.get( + "CORS_ALLOW_CREDENTIALS", + "true") + expose_headers = self.env.get( + "CORS_EXPOSE_HEADERS", + "Content-Length,X-My-Header") + allow_headers = self.env.get( + "CORS_ALLOW_HEADERS", + "Accept,Authorization,Content-Type,X-Custom-Header") + max_age = self.env.get( + "CORS_MAX_AGE", + "3600") + cors_conf = CORS_TEMPLACE.format( + allow_origin=allow_origin, + allow_methods=allow_methods, + allow_credentials=allow_credentials, + expose_headers=expose_headers, + allow_headers=allow_headers, + max_age=max_age + ) + with open(self.cors_conf, "w") as cfile: + cfile.write(cors_conf) + + def buildout(self): + """ Buildout from environment variables + """ + # Already configured + if os.path.exists(self.custom_conf): + return + + findlinks = self.env.get("FIND_LINKS", "").strip().split() + + eggs = self.env.get("PLONE_ADDONS", + self.env.get("ADDONS", "")).strip().split() + + zcml = self.env.get("PLONE_ZCML", + self.env.get("ZCML", "")).strip().split() + + develop = self.env.get("PLONE_DEVELOP", + self.env.get("DEVELOP", "")).strip().split() + + site = self.env.get("PLONE_SITE", + self.env.get("SITE", "")).strip() + + profiles = self.env.get("PLONE_PROFILES", + self.env.get("PROFILES", "")).strip().split() + + versions = self.env.get("PLONE_VERSIONS", + self.env.get("VERSIONS", "")).strip().split() + + sources = self.env.get("SOURCES", "").strip().split(",") + + password = self.env.get("PASSWORD", "").strip() + + # If profiles not provided. Install ADDONS :default profiles + if not profiles: + for egg in eggs: + base = egg.split("=")[0] + profiles.append("%s:default" % base) + + if not (eggs or zcml or develop or site or password): + return + + buildout = BUILDOUT_TEMPLATE.format( + password=password or "admin", + findlinks="\n\t".join(findlinks), + eggs="\n\t".join(eggs), + zcml="\n\t".join(zcml), + develop="\n\t".join(develop), + versions="\n".join(versions), + sources="\n".join(sources), + ) + + if site: + buildout += PLONESITE_TEMPLATE.format( + site=site, + profiles="\n\t".join(profiles), + ) + + # If we need to create a senaitesite and we have a zeo setup + # configure collective.recipe.senaitesite properly + server = self.env.get("ZEO_ADDRESS", None) + if server: + buildout += ZEO_INSTANCE_TEMPLATE.format( + zeoaddress=server, + ) + + with open(self.custom_conf, 'w') as cfile: + cfile.write(buildout) + + def setup(self, **kwargs): + self.buildout() + self.cors() + self.zeoclient() + self.zeopack() + self.zeoserver() + + __call__ = setup + + +ZEO_TEMPLATE = """ + + read-only {read_only} + read-only-fallback {zeo_client_read_only_fallback} + blob-dir /data/blobstorage + shared-blob-dir {shared_blob_dir} + server {zeo_address} + storage {zeo_storage} + name zeostorage + var /home/senaite/senaitelims/parts/instance/var + cache-size {zeo_client_cache_size} + +""".strip() + +CORS_TEMPLACE = """ + + + + +""" + +BUILDOUT_TEMPLATE = """ +[buildout] +extends = buildout.cfg +user=admin:{password} +find-links += {findlinks} +develop += {develop} +eggs += {eggs} +zcml += {zcml} + +[versions] +{versions} + +[sources] +{sources} +""" + +PLONESITE_TEMPLATE = """ + +[plonesite] +enabled = true +site-id = {site} +profiles += {profiles} +""" + +ZEO_INSTANCE_TEMPLATE = """ + +[instance] +zeo-client = true +zeo-address = {zeoaddress} +shared-blob = off +http-fast-listen = off +""" + + +def initialize(): + """ Configure Instance as ZEO Client + """ + environment = Environment() + environment.setup() + + +if __name__ == "__main__": + initialize() diff --git a/2.5.0/requirements.txt b/2.5.0/requirements.txt new file mode 100644 index 0000000..37660fa --- /dev/null +++ b/2.5.0/requirements.txt @@ -0,0 +1 @@ +-r https://dist.plone.org/release/5.2.14/requirements.txt diff --git a/2.5.0/run_deps.txt b/2.5.0/run_deps.txt new file mode 100644 index 0000000..33164ae --- /dev/null +++ b/2.5.0/run_deps.txt @@ -0,0 +1,14 @@ +git +gosu +libcairo2 +libgdk-pixbuf2.0-0 +libjpeg62 +libopenjp2-7 +libpango-1.0-0 +libpangocairo-1.0-0 +libtiff5 +libxml2 +libxslt1.1 +lynx +rsync +vim diff --git a/latest/Dockerfile b/latest/Dockerfile index f0a7c11..157753f 100644 --- a/latest/Dockerfile +++ b/latest/Dockerfile @@ -9,8 +9,8 @@ LABEL senaite.core.version="latest" # Set environment variables ENV PLONE_MAJOR=5.2 \ PLONE_VERSION=5.2.13 \ - PLONE_MD5=12c037fae9413385149e8677f8457b84 \ - PLONE_UNIFIED_INSTALLER=Plone-5.2.13-UnifiedInstaller-1.0 \ + PLONE_MD5=e8e1f774f069026319be3038631e0734 \ + PLONE_UNIFIED_INSTALLER=Plone-5.2.14-UnifiedInstaller-1.0 \ SENAITE_HOME=/home/senaite \ SENAITE_USER=senaite \ SENAITE_INSTANCE_HOME=/home/senaite/senaitelims \ diff --git a/latest/requirements.txt b/latest/requirements.txt index 9949472..37660fa 100644 --- a/latest/requirements.txt +++ b/latest/requirements.txt @@ -1 +1 @@ --r https://dist.plone.org/release/5.2.13/requirements.txt +-r https://dist.plone.org/release/5.2.14/requirements.txt diff --git a/stack.yml b/stack.yml index becbe84..d60953a 100644 --- a/stack.yml +++ b/stack.yml @@ -1,7 +1,7 @@ version: "3" services: instance: - image: senaite/senaite:v2.4.1 + image: senaite/senaite:v2.5.0 restart: "unless-stopped" ports: - "8080:8080"