diff --git a/2.0.0rc3/Dockerfile b/2.0.0rc3/Dockerfile new file mode 100644 index 0000000..69f2a36 --- /dev/null +++ b/2.0.0rc3/Dockerfile @@ -0,0 +1,69 @@ +# Use an official Python runtime as a parent image +FROM python:2.7-stretch + +# Set one or more individual labels +LABEL maintainer="Ramon Bartl" +LABEL email="rb@ridingbytes.com" +LABEL senaite.core.version="2.0.0rc2" + +# Set environment variables +ENV PLONE_MAJOR=5.2 \ + PLONE_VERSION=5.2.2 \ + PLONE_MD5=a603eddfd3abb0528f0861472ebac934 \ + PLONE_UNIFIED_INSTALLER=Plone-5.2.2-UnifiedInstaller \ + 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 direcotries +RUN mkdir -p $SENAITE_INSTANCE_HOME $SENAITE_FILESTORAGE $SENAITE_BLOBSTORAGE + +# Copy the package config +COPY packages.txt / + +# Install package dependencies +RUN apt-get update && apt-get install -y --no-install-recommends $(grep -vE "^\s*#" /packages.txt | tr "\n" " ") + +# Fetch unified installer +RUN 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 \ + && cd $SENAITE_HOME \ + && rm -rf /$PLONE_UNIFIED_INSTALLER /Plone.tgz + +# Change working directory +WORKDIR $SENAITE_INSTANCE_HOME + +# Copy Buildout +COPY requirements.txt buildout.cfg ./ + +# Buildout +RUN 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 + +# Mount external volume +VOLUME /data + +# Copy startup scripts +COPY docker-initialize.py docker-entrypoint.sh / + +# Expose instance port +EXPOSE 8080 + +# Add instance healthcheck +HEALTHCHECK --interval=1m --timeout=5s --start-period=1m \ + CMD nc -z -w5 127.0.0.1 8080 || exit 1 + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["start"] diff --git a/2.0.0rc3/buildout.cfg b/2.0.0rc3/buildout.cfg new file mode 100644 index 0000000..50f11cc --- /dev/null +++ b/2.0.0rc3/buildout.cfg @@ -0,0 +1,53 @@ +[buildout] +extends = + buildout-base.cfg + https://dist.plone.org/release/5.2.3/versions.cfg +find-links += + https://dist.plone.org/release/5.2.3/ + https://dist.plone.org/thirdparty/ + +extensions = + +index = https://pypi.python.org/simple/ + +var-dir=/data +user=admin:admin + +effective-user = senaite +buildout-user = senaite + +eggs-directory=../buildout-cache/eggs +download-cache=../buildout-cache/downloads + +parts += + zeo + plonesite + + +eggs += + senaite.lims + +[client1] +recipe = + +[zeo] +<= zeoserver_base +recipe = plone.recipe.zeoserver +zeo-address = 8080 + +[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=42.0.2 +zc.buildout=2.13.3 +senaite.lims=2.0.0rc3 +senaite.jsonapi=2.0.0rc2 \ No newline at end of file diff --git a/2.0.0rc3/docker-compose.yml b/2.0.0rc3/docker-compose.yml new file mode 100644 index 0000000..91767ca --- /dev/null +++ b/2.0.0rc3/docker-compose.yml @@ -0,0 +1,11 @@ +version: "2" +services: + senaite: + image: senaite + volumes: + - data:/data + ports: + - "8080:8080" + +volumes: + data: diff --git a/2.0.0rc3/docker-entrypoint.sh b/2.0.0rc3/docker-entrypoint.sh new file mode 100755 index 0000000..3d1954b --- /dev/null +++ b/2.0.0rc3/docker-entrypoint.sh @@ -0,0 +1,41 @@ +#!/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 + +if [ -e "custom.cfg" ]; then + if [ ! -e "bin/develop" ]; 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 +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.0.0rc3/docker-initialize.py b/2.0.0rc3/docker-initialize.py new file mode 100755 index 0000000..cd2ba5e --- /dev/null +++ b/2.0.0rc3/docker-initialize.py @@ -0,0 +1,265 @@ +#!/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(",") + + # If profiles not provided. Install ADDONS :default profiles + if not profiles: + for egg in eggs: + base = egg.split("=")[0] + profiles.append("%s:default" % base) + + enabled = bool(site) + if not (eggs or zcml or develop or enabled): + return + + buildout = BUILDOUT_TEMPLATE.format( + findlinks="\n\t".join(findlinks), + eggs="\n\t".join(eggs), + zcml="\n\t".join(zcml), + develop="\n\t".join(develop), + profiles="\n\t".join(profiles), + versions="\n".join(versions), + sources="\n".join(sources), + site=site or "senaite", + enabled=enabled, + ) + + # 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 = develop.cfg +find-links += {findlinks} +develop += {develop} +eggs += {eggs} +zcml += {zcml} + +[plonesite] +enabled = {enabled} +site-id = {site} +profiles += {profiles} + +[versions] +{versions} + +[sources] +{sources} +""" + +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.0.0rc3/packages.txt b/2.0.0rc3/packages.txt new file mode 100644 index 0000000..bd84b1b --- /dev/null +++ b/2.0.0rc3/packages.txt @@ -0,0 +1,28 @@ +dpkg-dev +gcc +gosu +libbz2-dev +libc6-dev +libcairo2 +libffi-dev +libgdk-pixbuf2.0-0 +libjpeg62 +libjpeg62-turbo-dev +libopenjp2-7 +libopenjp2-7-dev +libpango-1.0-0 +libpangocairo-1.0-0 +libpcre3-dev +libssl-dev +libtiff5 +libtiff5-dev +libxml2 +libxml2-dev +libxslt1-dev +libxslt1.1 +lynx +netcat +rsync +wget +wv +zlib1g-dev diff --git a/2.0.0rc3/requirements.txt b/2.0.0rc3/requirements.txt new file mode 100644 index 0000000..b65a7a0 --- /dev/null +++ b/2.0.0rc3/requirements.txt @@ -0,0 +1,3 @@ +setuptools==42.0.2 +zc.buildout==2.13.3 +wheel