├── .dockerignore ├── .github └── workflows │ ├── description.yml │ ├── image-release.yml │ ├── nightly.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── Dockerfile.acceptance ├── Dockerfile.builder ├── Dockerfile.classicui ├── Dockerfile.dev ├── Dockerfile.nightly ├── Dockerfile.prod ├── LICENSE.txt ├── Makefile ├── README.md ├── helpers ├── compile_mo.py └── pip-from-buildout-coredev.py ├── news ├── .towncrier_template.jinja └── 189.feat ├── skeleton ├── docker-entrypoint.sh ├── etc │ ├── package-includes │ │ └── .gitkeep │ ├── relstorage.conf │ ├── relstorage_pack.conf │ ├── site.zcml │ ├── zeo.conf │ ├── zope.conf │ ├── zope.conf.d │ │ └── .gitkeep │ └── zope.ini ├── inituser └── scripts │ ├── cors.py │ ├── create_site.py │ ├── default.json │ └── pack.py ├── test ├── README.md ├── config.sh ├── retry.sh ├── run.sh └── tests │ ├── no-hard-coded-passwords │ └── run.sh │ ├── override-cmd │ └── run.sh │ ├── plone-addons │ ├── expected-std-out.txt │ └── run.sh │ ├── plone-arbitrary-user │ └── run.sh │ ├── plone-basics │ └── run.sh │ ├── plone-cors │ ├── expected-std-out.txt │ └── run.sh │ ├── plone-develop │ ├── expected-std-out.txt │ ├── helloworld │ │ ├── helloworld │ │ │ └── __init__.py │ │ └── setup.py │ └── run.sh │ ├── plone-listenport │ └── run.sh │ ├── plone-relstorage │ └── run.sh │ ├── plone-shared-blob-dir │ └── run.sh │ ├── plone-site │ └── run.sh │ ├── plone-zeoclient │ └── run.sh │ └── utc │ ├── expected-std-out.txt │ └── run.sh ├── towncrier.toml └── version.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | tests 4 | -------------------------------------------------------------------------------- /.github/workflows/description.yml: -------------------------------------------------------------------------------- 1 | name: Update Docker Hub Description 2 | 3 | on: 4 | push: 5 | branches: 6 | - '6.1.x' 7 | paths: 8 | - "README.md" 9 | - .github/workflows/description.yml 10 | 11 | jobs: 12 | update-description: 13 | runs-on: ubuntu-latest 14 | environment: DOCKER_HUB 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Update repo description 20 | uses: peter-evans/dockerhub-description@v4 21 | with: 22 | username: ${{ secrets.DOCKERHUB_USERNAME }} 23 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 24 | repository: plone/plone-backend 25 | readme-filepath: README.md 26 | -------------------------------------------------------------------------------- /.github/workflows/image-release.yml: -------------------------------------------------------------------------------- 1 | name: Release Image to Container registry 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | plone-version: 7 | required: true 8 | type: string 9 | image-name: 10 | required: true 11 | type: string 12 | dockerfile: 13 | required: true 14 | type: string 15 | platforms: 16 | required: true 17 | type: string 18 | load: 19 | required: false 20 | type: boolean 21 | default: false 22 | push: 23 | required: false 24 | type: boolean 25 | default: false 26 | is-latest: 27 | required: false 28 | type: boolean 29 | default: false 30 | secrets: 31 | ghcr-registry-username: 32 | required: true 33 | ghcr-registry-password: 34 | required: true 35 | docker-registry-username: 36 | required: true 37 | docker-registry-password: 38 | required: true 39 | 40 | jobs: 41 | 42 | release: 43 | runs-on: [self-hosted, ARM64, stable] 44 | 45 | environment: DOCKER_HUB 46 | steps: 47 | - name: Checkout 48 | uses: actions/checkout@v4 49 | 50 | - name: Docker meta 51 | id: meta 52 | uses: docker/metadata-action@v5 53 | with: 54 | images: | 55 | ${{ inputs.image-name }} 56 | flavor: | 57 | latest=false 58 | tags: | 59 | type=ref,event=branch 60 | type=ref,event=pr 61 | type=pep440,pattern={{version}}, value=${{ inputs.plone-version }} 62 | type=pep440,pattern={{major}}.{{minor}}.{{patch}}, value=${{ inputs.plone-version }} 63 | type=pep440,pattern={{major}}.{{minor}}, value=${{ inputs.plone-version }} 64 | type=pep440,pattern={{major}}, value=${{ inputs.plone-version }} 65 | type=sha 66 | type=raw,value=latest,enable=${{ inputs.is-latest }} 67 | type=raw,value=${{ inputs.plone-version }} 68 | 69 | - name: Set up QEMU 70 | uses: docker/setup-qemu-action@v3 71 | 72 | - name: Set up Docker Buildx 73 | uses: docker/setup-buildx-action@v3 74 | 75 | - name: Login to GitHub Registry 76 | uses: docker/login-action@v3 77 | with: 78 | registry: ghcr.io 79 | username: ${{ secrets.ghcr-registry-username }} 80 | password: ${{ secrets.ghcr-registry-password }} 81 | 82 | - name: Login to Docker Hub Registry 83 | uses: docker/login-action@v3 84 | with: 85 | username: ${{ secrets.docker-registry-username }} 86 | password: ${{ secrets.docker-registry-password }} 87 | 88 | - name: Build and push 89 | uses: docker/build-push-action@v6 90 | with: 91 | platforms: ${{ inputs.platforms }} 92 | context: . 93 | file: ${{ inputs.dockerfile }} 94 | build-args: | 95 | PLONE_VERSION=${{ inputs.plone-version }} 96 | push: ${{ inputs.push && github.event_name != 'pull_request' }} 97 | tags: ${{ steps.meta.outputs.tags }} 98 | labels: ${{ steps.meta.outputs.labels }} 99 | load: ${{ inputs.load }} 100 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Generate Nightly Image 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '15 22 * * *' 7 | 8 | jobs: 9 | 10 | release: 11 | runs-on: ubuntu-latest 12 | environment: DOCKER_HUB 13 | 14 | steps: 15 | 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Docker meta 20 | id: meta 21 | uses: docker/metadata-action@v5 22 | with: 23 | # list of Docker images to use as base name for tags 24 | images: | 25 | plone/plone-backend 26 | # update label with proper version number 27 | labels: | 28 | org.label-schema.docker.cmd=docker run -d -p 8080:8080 plone/plone-backend:nightly 29 | # generate Docker tags based on the following events/attributes 30 | tags: | 31 | type=raw,value=nightly 32 | type=schedule,pattern=nightly-{{date 'YYYYMMDD'}} 33 | 34 | - name: Set up QEMU 35 | uses: docker/setup-qemu-action@v3 36 | 37 | - name: Set up Docker Buildx 38 | uses: docker/setup-buildx-action@v3 39 | 40 | - name: Login to DockerHub 41 | uses: docker/login-action@v3 42 | with: 43 | username: ${{ secrets.DOCKERHUB_USERNAME }} 44 | password: ${{ secrets.DOCKERHUB_TOKEN }} 45 | 46 | - name: Build and push 47 | uses: docker/build-push-action@v4 48 | with: 49 | platforms: linux/amd64,linux/arm64 50 | context: . 51 | file: Dockerfile.nightly 52 | push: ${{ github.event_name != 'pull_request' }} 53 | tags: ${{ steps.meta.outputs.tags }} 54 | labels: ${{ steps.meta.outputs.labels }} 55 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release new Docker image 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | workflow_dispatch: 8 | 9 | env: 10 | BASE_IMAGE_NAME: plone/server 11 | PLATFORMS: linux/amd64,linux/arm64 12 | IS_LATEST: false 13 | 14 | jobs: 15 | 16 | meta: 17 | runs-on: ubuntu-latest 18 | outputs: 19 | BASE_IMAGE_NAME: ${{ steps.vars.outputs.BASE_IMAGE_NAME }} 20 | IS_LATEST: ${{ steps.vars.outputs.IS_LATEST }} 21 | PLATFORMS: ${{ steps.vars.outputs.PLATFORMS }} 22 | PLONE_VERSION: ${{ steps.vars.outputs.PLONE_VERSION }} 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | 27 | - name: Set BASE_IMAGE_NAME, IS_LATEST, PLATFORMS, PLONE_VERSION 28 | id: vars 29 | run: | 30 | echo "BASE_IMAGE_NAME=$BASE_IMAGE_NAME" >> $GITHUB_OUTPUT 31 | echo "PLATFORMS=$PLATFORMS" >> $GITHUB_OUTPUT 32 | echo "IS_LATEST=$IS_LATEST" >> $GITHUB_OUTPUT 33 | echo "PLONE_VERSION=$(cat version.txt)" >> $GITHUB_OUTPUT 34 | 35 | builder-image: 36 | needs: 37 | - meta 38 | uses: ./.github/workflows/image-release.yml 39 | with: 40 | plone-version: ${{ needs.meta.outputs.PLONE_VERSION }} 41 | image-name: ${{ needs.meta.outputs.BASE_IMAGE_NAME }}-builder 42 | dockerfile: Dockerfile.builder 43 | platforms: ${{ needs.meta.outputs.PLATFORMS }} 44 | is-latest: ${{ needs.meta.outputs.IS_LATEST == 'true' }} 45 | load: false 46 | push: true 47 | secrets: 48 | ghcr-registry-username: ${{ github.actor }} 49 | ghcr-registry-password: ${{ secrets.GITHUB_TOKEN }} 50 | docker-registry-username: ${{ secrets.DOCKERHUB_USERNAME }} 51 | docker-registry-password: ${{ secrets.DOCKERHUB_TOKEN }} 52 | 53 | prod-conf-image: 54 | uses: ./.github/workflows/image-release.yml 55 | with: 56 | plone-version: ${{ needs.meta.outputs.PLONE_VERSION }} 57 | image-name: ${{ needs.meta.outputs.BASE_IMAGE_NAME }}-prod-config 58 | dockerfile: Dockerfile.prod 59 | platforms: ${{ needs.meta.outputs.PLATFORMS }} 60 | is-latest: ${{ needs.meta.outputs.IS_LATEST == 'true' }} 61 | load: false 62 | push: true 63 | secrets: 64 | ghcr-registry-username: ${{ github.actor }} 65 | ghcr-registry-password: ${{ secrets.GITHUB_TOKEN }} 66 | docker-registry-username: ${{ secrets.DOCKERHUB_USERNAME }} 67 | docker-registry-password: ${{ secrets.DOCKERHUB_TOKEN }} 68 | needs: 69 | - meta 70 | 71 | dev-image: 72 | uses: ./.github/workflows/image-release.yml 73 | with: 74 | plone-version: ${{ needs.meta.outputs.PLONE_VERSION }} 75 | image-name: ${{ needs.meta.outputs.BASE_IMAGE_NAME }}-dev 76 | dockerfile: Dockerfile.dev 77 | platforms: ${{ needs.meta.outputs.PLATFORMS }} 78 | is-latest: ${{ needs.meta.outputs.IS_LATEST == 'true' }} 79 | load: false 80 | push: true 81 | secrets: 82 | ghcr-registry-username: ${{ github.actor }} 83 | ghcr-registry-password: ${{ secrets.GITHUB_TOKEN }} 84 | docker-registry-username: ${{ secrets.DOCKERHUB_USERNAME }} 85 | docker-registry-password: ${{ secrets.DOCKERHUB_TOKEN }} 86 | needs: 87 | - meta 88 | - builder-image 89 | 90 | acceptance-image: 91 | uses: ./.github/workflows/image-release.yml 92 | with: 93 | plone-version: ${{ needs.meta.outputs.PLONE_VERSION }} 94 | image-name: ${{ needs.meta.outputs.BASE_IMAGE_NAME }}-acceptance 95 | dockerfile: Dockerfile.acceptance 96 | platforms: ${{ needs.meta.outputs.PLATFORMS }} 97 | is-latest: ${{ needs.meta.outputs.IS_LATEST == 'true' }} 98 | load: false 99 | push: true 100 | secrets: 101 | ghcr-registry-username: ${{ github.actor }} 102 | ghcr-registry-password: ${{ secrets.GITHUB_TOKEN }} 103 | docker-registry-username: ${{ secrets.DOCKERHUB_USERNAME }} 104 | docker-registry-password: ${{ secrets.DOCKERHUB_TOKEN }} 105 | needs: 106 | - meta 107 | - builder-image 108 | - prod-conf-image 109 | 110 | plone-backend: 111 | uses: ./.github/workflows/image-release.yml 112 | with: 113 | plone-version: ${{ needs.meta.outputs.PLONE_VERSION }} 114 | image-name: | 115 | ghcr.io/plone/server 116 | ghcr.io/plone/plone-backend 117 | plone/plone-backend 118 | dockerfile: Dockerfile 119 | platforms: ${{ needs.meta.outputs.PLATFORMS }} 120 | is-latest: ${{ needs.meta.outputs.IS_LATEST == 'true' }} 121 | load: false 122 | push: true 123 | secrets: 124 | ghcr-registry-username: ${{ github.actor }} 125 | ghcr-registry-password: ${{ secrets.GITHUB_TOKEN }} 126 | docker-registry-username: ${{ secrets.DOCKERHUB_USERNAME }} 127 | docker-registry-password: ${{ secrets.DOCKERHUB_TOKEN }} 128 | needs: 129 | - meta 130 | - builder-image 131 | - prod-conf-image 132 | 133 | plone-classicui: 134 | uses: ./.github/workflows/image-release.yml 135 | with: 136 | plone-version: ${{ needs.meta.outputs.PLONE_VERSION }} 137 | image-name: | 138 | ghcr.io/plone/plone-classicui 139 | plone/plone-classicui 140 | dockerfile: Dockerfile.classicui 141 | platforms: ${{ needs.meta.outputs.PLATFORMS }} 142 | is-latest: ${{ needs.meta.outputs.IS_LATEST == 'true' }} 143 | load: false 144 | push: true 145 | secrets: 146 | ghcr-registry-username: ${{ github.actor }} 147 | ghcr-registry-password: ${{ secrets.GITHUB_TOKEN }} 148 | docker-registry-username: ${{ secrets.DOCKERHUB_USERNAME }} 149 | docker-registry-password: ${{ secrets.DOCKERHUB_TOKEN }} 150 | needs: 151 | - meta 152 | - builder-image 153 | - prod-conf-image 154 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test generated image 2 | 3 | on: 4 | push: 5 | branches: 6 | - "6.1.x" 7 | pull_request: 8 | branches: 9 | - "6.1.x" 10 | 11 | jobs: 12 | 13 | meta: 14 | runs-on: ubuntu-latest 15 | outputs: 16 | SHA: ${{ steps.vars.outputs.SHA }} 17 | PLONE_VERSION: ${{ steps.vars.outputs.PLONE_VERSION }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Compute SHA value to be used in building the main image 23 | id: vars 24 | run: | 25 | echo "SHA=sha-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT 26 | echo "PLONE_VERSION=$(cat version.txt)" >> $GITHUB_OUTPUT 27 | 28 | test: 29 | runs-on: ubuntu-latest 30 | environment: DOCKER_HUB 31 | needs: 32 | - meta 33 | steps: 34 | 35 | - name: Checkout 36 | uses: actions/checkout@v4 37 | 38 | - name: Builder Image 39 | id: meta-builder 40 | uses: docker/metadata-action@v5 41 | with: 42 | images: | 43 | plone/server-builder 44 | tags: | 45 | type=sha 46 | 47 | - name: Prod Config Image 48 | id: meta-prod-config 49 | uses: docker/metadata-action@v5 50 | with: 51 | images: | 52 | plone/server-prod-config 53 | tags: | 54 | type=sha 55 | 56 | - name: Main Image 57 | id: meta-main 58 | uses: docker/metadata-action@v5 59 | with: 60 | images: | 61 | plone/plone-backend 62 | tags: | 63 | type=sha 64 | 65 | - name: Set up Docker Buildx 66 | uses: docker/setup-buildx-action@v3 67 | 68 | - name: Login to Docker Hub Container Registry 69 | uses: docker/login-action@v3 70 | with: 71 | username: ${{ secrets.DOCKERHUB_USERNAME }} 72 | password: ${{ secrets.DOCKERHUB_TOKEN }} 73 | 74 | - name: Build builder image for testing 75 | uses: docker/build-push-action@v4 76 | with: 77 | context: ./ 78 | file: Dockerfile.builder 79 | tags: ${{ steps.meta-builder.outputs.tags }} 80 | push: true 81 | build-args: | 82 | PLONE_VERSION=${{ needs.meta.outputs.PLONE_VERSION }} 83 | 84 | - name: Build prod-config image for testing 85 | uses: docker/build-push-action@v4 86 | with: 87 | context: ./ 88 | file: Dockerfile.prod 89 | tags: ${{ steps.meta-prod-config.outputs.tags }} 90 | push: true 91 | 92 | - name: Build main image for testing 93 | uses: docker/build-push-action@v4 94 | with: 95 | context: ./ 96 | file: Dockerfile 97 | tags: ${{ steps.meta-main.outputs.tags }} 98 | push: true 99 | build-args: | 100 | PLONE_VERSION=${{ needs.meta.outputs.SHA }} 101 | 102 | - name: Test 103 | run: | 104 | docker pull ${{ steps.meta-main.outputs.tags }} 105 | ./test/run.sh ${{ steps.meta-main.outputs.tags }} 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | # pyenv 4 | .python-version 5 | __pycache__ 6 | *.egg-info 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 6.1.1 (2025-03-25) 2 | 3 | 4 | - Upgrade to Plone 6.1.1 final. 5 | 6 | 7 | ## 6.1.1rc2 (2025-03-21) 8 | 9 | 10 | - Upgrade to Plone 6.1.1rc2 11 | 12 | 13 | ## 6.1.1rc1 (2025-03-17) 14 | 15 | ### Features (also collected from previous changes 16 | 17 | - Provide command to pack ZODB. @erral [#104](https://github.com/plone/volto/issues/104) 18 | 19 | ### Bugfixes 20 | 21 | - Ensure that zope.conf customizations can never happen twice [Rudd-O] [#93](https://github.com/plone/volto/issues/93) 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | ARG PYTHON_VERSION=3.12 3 | ARG PLONE_VERSION 4 | FROM plone/server-builder:${PLONE_VERSION} AS builder 5 | 6 | ARG PLONE_VERSION 7 | 8 | FROM plone/server-prod-config:${PLONE_VERSION} 9 | 10 | ARG PYTHON_VERSION 11 | ARG PLONE_VERSION 12 | 13 | LABEL maintainer="Plone Community " \ 14 | org.label-schema.name="plone-backend" \ 15 | org.label-schema.description="Plone $PLONE_VERSION backend image using Python $PYTHON_VERSION" \ 16 | org.label-schema.vendor="Plone Foundation" 17 | 18 | # Use /app as the workdir 19 | WORKDIR /app 20 | 21 | # Copy /app from builder 22 | COPY --from=builder --chown=500:500 /app /app 23 | 24 | # Enable compilation of po files into mo files (This is added here for backward compatibility) 25 | 26 | ENV zope_i18n_compile_mo_files=true 27 | 28 | # Link /data (the exposed volume) into /app/var 29 | RUN ln -s /data /app/var 30 | -------------------------------------------------------------------------------- /Dockerfile.acceptance: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | ARG PYTHON_VERSION=3.12 3 | ARG PLONE_VERSION 4 | FROM plone/server-builder:${PLONE_VERSION} AS builder 5 | 6 | ARG PLONE_VERSION 7 | 8 | # Install robotframework support 9 | RUN /app/bin/pip install plone.app.robotframework>=2.0.0 10 | 11 | FROM plone/server-prod-config:${PLONE_VERSION} 12 | 13 | ARG PYTHON_VERSION 14 | ARG PLONE_VERSION 15 | 16 | LABEL maintainer="Plone Community " \ 17 | org.label-schema.name="server-acceptance" \ 18 | org.label-schema.description="Plone $PLONE_VERSION backend acceptance image using Python $PYTHON_VERSION" \ 19 | org.label-schema.vendor="Plone Foundation" 20 | 21 | 22 | # Copy /app from builder 23 | COPY --from=builder --chown=500:500 /app /app 24 | 25 | # Set chameleon cache directory 26 | ENV CHAMELEON_CACHE=/app/var/cache 27 | # Zope listens to all ip addresses 28 | ENV ZSERVER_HOST=0.0.0.0 29 | # Zope port to be 55001 30 | ENV ZSERVER_PORT=55001 31 | # This fixes the healthcheck defined in server-prod-config 32 | ENV LISTEN_PORT=${ZSERVER_PORT} 33 | # Profiles to be added to the created site 34 | ENV APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default 35 | # Packages to be used in configuration 36 | ENV CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors 37 | 38 | RUN ln -s /data /app/var 39 | 40 | # Also expose port 55001 41 | EXPOSE 55001 42 | 43 | # Entrypoint is robot-server 44 | ENTRYPOINT [ "/app/bin/robot-server" ] 45 | 46 | # Run VOLTO_ROBOT_TESTING by default 47 | CMD ["plone.app.robotframework.testing.VOLTO_ROBOT_TESTING"] 48 | -------------------------------------------------------------------------------- /Dockerfile.builder: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | ARG PYTHON_VERSION=3.12 3 | FROM python:${PYTHON_VERSION}-slim-bookworm 4 | 5 | ARG PLONE_VERSION 6 | 7 | ENV EXTRA_PACKAGES="relstorage==4.1.1 psycopg2==2.9.10 python-ldap==3.4.4 ZEO" 8 | 9 | 10 | LABEL maintainer="Plone Community " \ 11 | org.label-schema.name="server-builder" \ 12 | org.label-schema.description="Plone $PLONE_VERSION builder image with Python $PYTHON_VERSION" \ 13 | org.label-schema.vendor="Plone Foundation" 14 | 15 | 16 | # Script used for pre-compilation of po files 17 | COPY /helpers/compile_mo.py /compile_mo.py 18 | 19 | # Install Plone 20 | # - Install build dependencies 21 | # - Download constraints.txt file to /app folder 22 | # - Install Plone using pip 23 | # - Create /data folder 24 | # - Pre-compile po files in the /app/lib folder 25 | # - Remove .pyc and .pyo files 26 | # 27 | RUN < 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## Defensive settings for make: 2 | # https://tech.davis-hansson.com/p/make/ 3 | SHELL:=bash 4 | .ONESHELL: 5 | .SHELLFLAGS:=-xeu -o pipefail -O inherit_errexit -c 6 | .SILENT: 7 | .DELETE_ON_ERROR: 8 | MAKEFLAGS+=--warn-undefined-variables 9 | MAKEFLAGS+=--no-builtin-rules 10 | 11 | NIGHTLY_IMAGE_TAG=nightly 12 | 13 | # We like colors 14 | # From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects 15 | RED=`tput setaf 1` 16 | GREEN=`tput setaf 2` 17 | RESET=`tput sgr0` 18 | YELLOW=`tput setaf 3` 19 | 20 | # Current version 21 | MAIN_IMAGE_NAME=plone/plone-backend 22 | CLASSICUI_IMAGE_NAME=plone/plone-classicui 23 | BASE_IMAGE_NAME=plone/server 24 | PLONE_VERSION=$$(cat version.txt) 25 | PYTHON_VERSION=3.12 26 | IMAGE_TAG=${PLONE_VERSION} 27 | NIGHTLY_IMAGE_TAG=nightly 28 | 29 | # Code Quality 30 | CURRENT_FOLDER=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 31 | CODE_QUALITY_VERSION=2.1.1 32 | ifndef LOG_LEVEL 33 | LOG_LEVEL=INFO 34 | endif 35 | CURRENT_USER=$$(whoami) 36 | USER_INFO=$$(id -u ${CURRENT_USER}):$$(getent group ${CURRENT_USER}|cut -d: -f3) 37 | LINT=docker run --rm -e LOG_LEVEL="${LOG_LEVEL}" -v "${CURRENT_FOLDER}":/github/workspace plone/code-quality:${CODE_QUALITY_VERSION} check 38 | FORMAT=docker run --rm --user="${USER_INFO}" -e LOG_LEVEL="${LOG_LEVEL}" -v "${CURRENT_FOLDER}":/github/workspace plone/code-quality:${CODE_QUALITY_VERSION} format 39 | 40 | 41 | 42 | .PHONY: all 43 | all: help 44 | 45 | # Add the following 'help' target to your Makefile 46 | # And add help text after each target name starting with '\#\#' 47 | .PHONY: help 48 | help: # This help message 49 | @grep -E '^[a-zA-Z_-]+:.*?# .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?# "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 50 | 51 | # Format 52 | .PHONY: format 53 | format: ## Format the codebase according to our standards 54 | @echo "$(GREEN)==> Format Python helper$(RESET)" 55 | $(FORMAT) 56 | 57 | .PHONY: lint 58 | lint: ## check code style 59 | $(LINT) 60 | 61 | # Build image 62 | .PHONY: show-image 63 | show-image: ## Print Version 64 | @echo "$(MAIN_IMAGE_NAME):$(IMAGE_TAG)" 65 | @echo "$(MAIN_IMAGE_NAME):$(NIGHTLY_IMAGE_TAG)" 66 | @echo "$(BASE_IMAGE_NAME)-(builder|dev|prod-config|acceptance):$(IMAGE_TAG)" 67 | @echo "$(CLASSICUI_IMAGE_NAME):$(IMAGE_TAG)" 68 | 69 | .PHONY: image-builder 70 | image-builder: ## Build Base Image 71 | @echo "Building $(BASE_IMAGE_NAME)-builder:$(IMAGE_TAG)" 72 | @docker buildx build . --no-cache --build-arg PLONE_VERSION=${PLONE_VERSION} --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t $(BASE_IMAGE_NAME)-builder:$(IMAGE_TAG) -f Dockerfile.builder --load 73 | 74 | .PHONY: image-dev 75 | image-dev: ## Build Dev Image 76 | @echo "Building $(BASE_IMAGE_NAME)-dev:$(IMAGE_TAG)" 77 | @docker buildx build . --no-cache --build-arg PLONE_VERSION=${PLONE_VERSION} --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t $(BASE_IMAGE_NAME)-dev:$(IMAGE_TAG) -f Dockerfile.dev --load 78 | 79 | .PHONY: image-prod-config 80 | image-prod-config: ## Build Prod Image 81 | @echo "Building $(BASE_IMAGE_NAME)-prod-config:$(IMAGE_TAG)" 82 | @docker buildx build . --no-cache --build-arg PLONE_VERSION=${PLONE_VERSION} --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t $(BASE_IMAGE_NAME)-prod-config:$(IMAGE_TAG) -f Dockerfile.prod --load 83 | 84 | .PHONY: image-classicui 85 | image-classicui: ## Build Classic UI 86 | @echo "Building $(CLASSICUI_IMAGE_NAME):$(IMAGE_TAG)" 87 | @docker buildx build . --no-cache --build-arg PLONE_VERSION=${PLONE_VERSION} --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t $(CLASSICUI_IMAGE_NAME):$(IMAGE_TAG) -f Dockerfile.classicui --load 88 | 89 | .PHONY: image-acceptance 90 | image-acceptance: ## Build Acceptance Image 91 | @echo "Building $(BASE_IMAGE_NAME)-acceptance:$(IMAGE_TAG)" 92 | @docker buildx build . --no-cache --build-arg PLONE_VERSION=${PLONE_VERSION} --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t $(BASE_IMAGE_NAME)-acceptance:$(IMAGE_TAG) -f Dockerfile.acceptance --load 93 | 94 | .PHONY: image-main 95 | image-main: ## Build main image 96 | @echo "Building $(MAIN_IMAGE_NAME):$(IMAGE_TAG)" 97 | @docker buildx build . --no-cache --build-arg PLONE_VERSION=${PLONE_VERSION} --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t $(MAIN_IMAGE_NAME):$(IMAGE_TAG) -f Dockerfile --load 98 | 99 | .PHONY: image-nightly 100 | image-nightly: ## Build Docker Image Nightly 101 | @echo "Building $(MAIN_IMAGE_NAME):$(NIGHTLY_IMAGE_TAG)" 102 | @docker buildx build . --no-cache --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t $(MAIN_IMAGE_NAME):$(NIGHTLY_IMAGE_TAG) -f Dockerfile.nightly --load 103 | 104 | .PHONY: build-images 105 | build-images: ## Build Images 106 | @echo "Building $(BASE_IMAGE_NAME)-(builder|dev|prod):$(IMAGE_TAG) images" 107 | $(MAKE) image-builder 108 | $(MAKE) image-dev 109 | $(MAKE) image-prod-config 110 | $(MAKE) image-acceptance 111 | @echo "Building $(MAIN_IMAGE_NAME):$(IMAGE_TAG)" 112 | $(MAKE) image-main 113 | @echo "Building $(CLASSICUI_IMAGE_NAME):$(IMAGE_TAG)" 114 | $(MAKE) image-classicui 115 | 116 | create-tag: # Create a new tag using git 117 | @echo "Creating new tag $(PLONE_VERSION)" 118 | if git show-ref --tags v$(PLONE_VERSION) --quiet; then echo "$(PLONE_VERSION) already exists";else git tag -a v$(PLONE_VERSION) -m "Release $(PLONE_VERSION)" && git push && git push --tags;fi 119 | 120 | .PHONY: remove-tag 121 | remove-tag: # Remove an existing tag locally and remote 122 | @echo "Removing tag v$(IMAGE_TAG)" 123 | if git show-ref --tags v$(IMAGE_TAG) --quiet; then git tag -d v$(IMAGE_TAG) && git push origin :v$(IMAGE_TAG) && echo "$(IMAGE_TAG) removed";else echo "$(IMAGE_TAG) does not exist";fi 124 | 125 | commit-and-release: # Commit new version change and create tag 126 | @echo "Commiting changes" 127 | @git commit -am "Use Plone $(PLONE_VERSION)" 128 | make create-tag 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Plone Logo 3 |

4 | 5 |

6 | plone/plone-backend 7 |

8 | 9 |
10 | 11 | [![Docker Image Version (latest semver)](https://img.shields.io/docker/v/plone/plone-backend?sort=semver)](https://hub.docker.com/r/plone/plone-backend) 12 | [![Docker Image Size (latest semver)](https://img.shields.io/docker/image-size/plone/plone-backend?sort=semver)](https://hub.docker.com/r/plone/plone-backend) 13 | 14 | ![GitHub Repo stars](https://img.shields.io/github/stars/plone/plone-backend?style=flat-square) 15 | [![license badge](https://img.shields.io/github/license/plone/plone-backend)](./LICENSE) 16 | 17 |
18 | 19 | Plone backend [Docker](https://docker.com) images using Python 3 and [pip](https://pip.pypa.io/en/stable/). 20 | 21 | **Note:** 22 | These are the official images for the [Plone 6](https://plone.org/) release, together with [plone-frontend](https://github.com/plone/plone-frontend). 23 | These images are **not** Buildout based! 24 | 25 | ## Tags 26 | ### Supported tags and respective Dockerfile links 27 | 28 | | Plone Version | Tags | Dockerfile | 29 | | --- | --- | --- | 30 | | 6 | `6.0.14`, `6.0`, `6`, `latest` | [(6.0.x/Dockerfile)](https://github.com/plone/plone-backend/blob/v6.0.14/Dockerfile)| 31 | | 6.1 | `6.1.1`, `6.1` | [(6.1.x/Dockerfile)](https://github.com/plone/plone-backend/blob/v6.1.1/Dockerfile)| 32 | | 6 (nightly) | `nightly` | [(Dockerfile.nightly)](https://github.com/plone/plone-backend/blob/6.0.x/Dockerfile.nightly) | 33 | 34 | **Possible breaking changes in upcoming Plone 6.1 dependencies** 35 | 36 | Please note that the backend images in the 6.1.x series have several updated dependencies, which you should verify and test before using them in any existing project. Even if you extend from these images. The underlying OS has been switched from Debian 11 buster to 12 bookworm. Python is updated from 37 | 3.11 to 3.12. Relstorage has a new major release going from 3.x to 4.1. And there are are minor updates for libldap, libtiff and psycopg2 OS and Python libraries. 38 | 39 | ### Unsupported tags 40 | 41 | **Note:** 42 | These images for Plone 5 are **not** officially supported by the Plone community. 43 | 44 | 45 | | Plone Version | Tags | Dockerfile | 46 | | --- | --- | --- | 47 | | 5.2 | `5`, `5.2`, `5.2.14` | [(5.2.x/Dockerfile)](https://github.com/plone/plone-backend/blob/v5.2.14/Dockerfile) | 48 | 49 | 50 | See also the official [Buildout-based Plone 5 images](https://hub.docker.com/_/plone). 51 | 52 | ## Usage 53 | 54 | Please refer to the [Official Plone Documentation](https://6.docs.plone.org/install/containers/images/backend.html) for further documentation and examples. 55 | 56 | ## Contribute 57 | 58 | - [Issue Tracker](https://github.com/plone/plone-backend/issues) 59 | - [Source Code](https://github.com/plone/plone-backend/) 60 | - [Documentation](https://6.docs.plone.org/install/containers/images/backend.html) 61 | 62 | Please **DO NOT** commit to version branches directly. Even for the smallest and most trivial fix. 63 | **ALWAYS** open a pull request and ask somebody else to merge your code. **NEVER** merge it yourself. 64 | 65 | ## Credits 66 | 67 | This project is supported by: 68 | 69 | [![Plone Foundation](https://raw.githubusercontent.com/plone/.github/main/plone-foundation.png)](https://plone.org/) 70 | 71 | ## License 72 | 73 | The project is licensed under GPLv2. 74 | -------------------------------------------------------------------------------- /helpers/compile_mo.py: -------------------------------------------------------------------------------- 1 | """Helper script to pre-compile PO files in a Plone backend builder image.""" 2 | from pathlib import Path 3 | from pythongettext.msgfmt import Msgfmt 4 | from pythongettext.msgfmt import PoSyntaxError 5 | from typing import Generator 6 | 7 | import logging 8 | import os 9 | 10 | 11 | # Allow you to change the base folder of the application 12 | APP_FOLDER: str = os.environ.get("APP_FOLDER", "/app") 13 | LOG_FOLDER: str = "/var/log" 14 | 15 | 16 | logging.basicConfig(filename=Path(f"{LOG_FOLDER}/compile_mo.log")) 17 | logger = logging.getLogger("compile-mo") 18 | logger.setLevel(logging.DEBUG) 19 | 20 | 21 | def compile_po_file(po_file: Path) -> Path | None: 22 | """Compile a single PO file.""" 23 | # Search for a .mo file in the same directory 24 | parent: Path = po_file.parent 25 | domain: str = po_file.name[: -len(po_file.suffix)] 26 | mo_file: Path = parent / f"{domain}.mo" 27 | do_compile: bool = True 28 | 29 | if mo_file.exists(): 30 | # Check if an existing file needs to be recompiled 31 | do_compile = os.stat(mo_file).st_mtime < os.stat(po_file).st_mtime 32 | 33 | if do_compile: 34 | try: 35 | mo = Msgfmt(f"{po_file}", name=domain).getAsFile() 36 | except PoSyntaxError: 37 | logger.error(f" -- Error while compiling language file {mo_file}") 38 | return 39 | else: 40 | with open(mo_file, "wb") as f_out: 41 | try: 42 | f_out.write(mo.read()) 43 | except (IOError, OSError): 44 | logger.error(f" -- Error writing language file {mo_file}") 45 | return 46 | logger.info(f" -- Compiled language file {mo_file}") 47 | else: 48 | logger.info(f" -- Already coimpiled language file {mo_file}") 49 | return mo_file 50 | 51 | 52 | def main(): 53 | mo_files: list = [] 54 | lib_paths: list = [] 55 | lib_paths = [ 56 | Path(f"{APP_FOLDER}/lib").resolve(), 57 | Path(f"{APP_FOLDER}/src/sources").resolve(), 58 | ] 59 | for lib_path in lib_paths: 60 | po_files: Generator = lib_path.glob("**/*.po") 61 | logger.info("Starting compilation of po files") 62 | for po_file in po_files: 63 | mo_file = compile_po_file(po_file) 64 | if mo_file: 65 | mo_files.append(mo_file) 66 | logger.info(f"Compiled {len(mo_files)} files") 67 | 68 | 69 | if __name__ == "__main__": 70 | main() 71 | -------------------------------------------------------------------------------- /helpers/pip-from-buildout-coredev.py: -------------------------------------------------------------------------------- 1 | """Parse versions and sources from buildout.cfg and write files to be used with pip. 2 | 3 | Manual usage: python pip-from-buildout-coredev.py 4 | zc.buildout needs to be importable in that python. 5 | """ 6 | from pathlib import Path 7 | from zc.buildout import buildout 8 | 9 | 10 | import re 11 | 12 | 13 | PATTERN = f"pushurl=git@(?P[^ ]*)\.git .*branch=(?P[^ ]*).*$" 14 | IGNORE = ("docs", "jquery.recurrenceinput.js ", "mockup", "plone.themepreview") 15 | 16 | 17 | config_file = Path("buildout.cfg").resolve() 18 | constraints_file = Path("pip-constraints.txt").resolve() 19 | requirements_file = Path("pip-requirements.txt").resolve() 20 | 21 | config = buildout.Buildout(str(config_file), []) 22 | 23 | checkouts = config["buildout"]["auto-checkout"].split("\n") 24 | 25 | constraints = {} 26 | for package, version in sorted(config.versions.items()): 27 | constraints[package] = f"{package}=={version}" 28 | 29 | sources = {} 30 | for package in checkouts: 31 | source_info = config["sources"][package] 32 | match=re.search(PATTERN, source_info) 33 | if not match: 34 | # Wrong format, ignore 35 | continue 36 | match = match.groupdict() 37 | repo = match["repo"].replace(":", "/") 38 | branch = match["branch"] 39 | # sources[package] = f"git+https://{repo}@{branch}" 40 | sources[package] = f"https://{repo}/archive/refs/heads/{branch}.zip" 41 | 42 | 43 | # Generate requirements 44 | with open(requirements_file, "w") as cfile: 45 | cfile.write(f"# File created by {__file__}\n") 46 | cfile.write(f"# Data parsed from {config_file}\n") 47 | for package, info in sources.items(): 48 | if package in IGNORE: 49 | continue 50 | cfile.write(f"{info}\n") 51 | 52 | 53 | # Generate constraints 54 | with open(constraints_file, "w") as cfile: 55 | cfile.write(f"# File created by {__file__}\n") 56 | cfile.write(f"# Data parsed from {config_file}\n") 57 | for package, info in constraints.items(): 58 | if package in sources: 59 | continue 60 | cfile.write(f"{info}\n") 61 | -------------------------------------------------------------------------------- /news/.towncrier_template.jinja: -------------------------------------------------------------------------------- 1 | {% if sections[""] %} 2 | {% for category, val in definitions.items() if category in sections[""] %} 3 | ### {{ definitions[category]['name'] }} 4 | 5 | {% for text, values in sections[""][category].items() %} 6 | - {{ text }} {{ values|join(', ') }} 7 | {% endfor %} 8 | 9 | {% endfor %} 10 | {% endif %} -------------------------------------------------------------------------------- /news/189.feat: -------------------------------------------------------------------------------- 1 | Compile po files in the sources folder, where by default the packages from mx.ini are downloaded @erral 2 | -------------------------------------------------------------------------------- /skeleton/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | export VENVBIN=/app/bin 4 | 5 | if [ -z "${PIP_PARAMS}" ]; then 6 | PIP_PARAMS="" 7 | fi 8 | 9 | if [ -z "${CLIENT_HOME}" ]; then 10 | CLIENT_HOME="/data/$(hostname)/$(hostid)" 11 | export CLIENT_HOME=$CLIENT_HOME 12 | fi 13 | 14 | USER="$(id -u)" 15 | 16 | # Create directories to be used by Plone 17 | mkdir -p /data/filestorage /data/blobstorage /data/cache /data/log $CLIENT_HOME 18 | if [ "$USER" = '0' ]; then 19 | find /data -not -user plone -exec chown plone:plone {} \+ 20 | sudo="gosu plone" 21 | else 22 | sudo="" 23 | fi 24 | 25 | # MAIN ENV Vars 26 | [ -z ${SECURITY_POLICY_IMPLEMENTATION+x} ] && export SECURITY_POLICY_IMPLEMENTATION=C 27 | [ -z ${VERBOSE_SECURITY+x} ] && export VERBOSE_SECURITY=off 28 | [ -z ${DEFAULT_ZPUBLISHER_ENCODING+x} ] && export DEFAULT_ZPUBLISHER_ENCODING=utf-8 29 | [ -z ${COMPILE_MO_FILES+x} ] && export COMPILE_MO_FILES=true 30 | [ -z ${DEBUG_MODE+x} ] && export DEBUG_MODE=off 31 | [ -z ${ZOPE_FORM_MEMORY_LIMIT+x} ] && export ZOPE_FORM_MEMORY_LIMIT=4MB 32 | [ -z ${ZOPE_FORM_DISK_LIMIT+x} ] && export ZOPE_FORM_DISK_LIMIT=1GB 33 | [ -z ${ZOPE_FORM_MEMFILE_LIMIT+x} ] && export ZOPE_FORM_MEMFILE_LIMIT=4KB 34 | 35 | 36 | # ZODB ENV Vars 37 | [ -z ${ZODB_CACHE_SIZE+x} ] && export ZODB_CACHE_SIZE=50000 38 | 39 | if [[ -v RELSTORAGE_DSN ]]; then 40 | MSG="Using Relstorage configuration" 41 | CONF=relstorage.conf 42 | # Relstorage ENV Vars 43 | [ -z ${RELSTORAGE_NAME+x} ] && export RELSTORAGE_NAME=storage 44 | [ -z ${RELSTORAGE_READ_ONLY+x} ] && export RELSTORAGE_READ_ONLY=off 45 | [ -z ${RELSTORAGE_KEEP_HISTORY+x} ] && export RELSTORAGE_KEEP_HISTORY=true 46 | [ -z ${RELSTORAGE_COMMIT_LOCK_TIMEOUT+x} ] && export RELSTORAGE_COMMIT_LOCK_TIMEOUT=30 47 | [ -z ${RELSTORAGE_CREATE_SCHEMA+x} ] && export RELSTORAGE_CREATE_SCHEMA=true 48 | [ -z ${RELSTORAGE_SHARED_BLOB_DIR+x} ] && export RELSTORAGE_SHARED_BLOB_DIR=false 49 | [ -z ${RELSTORAGE_BLOB_CACHE_SIZE+x} ] && export RELSTORAGE_BLOB_CACHE_SIZE=100mb 50 | [ -z ${RELSTORAGE_BLOB_CACHE_SIZE_CHECK+x} ] && export RELSTORAGE_BLOB_CACHE_SIZE_CHECK=10 51 | [ -z ${RELSTORAGE_BLOB_CACHE_SIZE_CHECK_EXTERNAL+x} ] && export RELSTORAGE_BLOB_CACHE_SIZE_CHECK_EXTERNAL=false 52 | [ -z ${RELSTORAGE_BLOB_CHUNK_SIZE+x} ] && export RELSTORAGE_BLOB_CHUNK_SIZE=1048576 53 | [ -z ${RELSTORAGE_CACHE_LOCAL_MB+x} ] && export RELSTORAGE_CACHE_LOCAL_MB=10 54 | [ -z ${RELSTORAGE_CACHE_LOCAL_OBJECT_MAX+x} ] && export RELSTORAGE_CACHE_LOCAL_OBJECT_MAX=16384 55 | [ -z ${RELSTORAGE_CACHE_LOCAL_COMPRESSION+x} ] && export RELSTORAGE_CACHE_LOCAL_COMPRESSION=none 56 | [ -z ${RELSTORAGE_CACHE_DELTA_SIZE_LIMIT+x} ] && export RELSTORAGE_CACHE_DELTA_SIZE_LIMIT=100000 57 | elif [[ -v ZEO_ADDRESS ]]; then 58 | MSG="Using ZEO configuration" 59 | CONF=zeo.conf 60 | # Check ZEO variables 61 | [ -z ${ZEO_SHARED_BLOB_DIR+x} ] && export ZEO_SHARED_BLOB_DIR=off 62 | [ -z ${ZEO_READ_ONLY+x} ] && export ZEO_READ_ONLY=false 63 | [ -z ${ZEO_CLIENT_READ_ONLY_FALLBACK+x} ] && export ZEO_CLIENT_READ_ONLY_FALLBACK=false 64 | [ -z ${ZEO_STORAGE+x} ] && export ZEO_STORAGE=1 65 | [ -z ${ZEO_CLIENT_CACHE_SIZE+x} ] && export ZEO_CLIENT_CACHE_SIZE=128MB 66 | [ -z ${ZEO_DROP_CACHE_RATHER_VERIFY+x} ] && export ZEO_DROP_CACHE_RATHER_VERIFY=false 67 | else 68 | MSG="Using default configuration" 69 | CONF=zope.conf 70 | fi 71 | 72 | # Add anything inside etc/zope.conf.d to the configuration file 73 | # prior to starting the respective Zope server. 74 | # This provides a counterpart for the ZCML package-includes 75 | # functionality, but for Zope configuration snippets. 76 | # 77 | # This must be executed only once during the container lifetime, 78 | # as container can be stopped and then restarted... double-additions 79 | # of the same snippet cause the Zope server not to start. 80 | if grep -q '# Runtime customizations:' etc/${CONF} ; then 81 | # Note in the log this was customized. Useful for bug reports. 82 | MSG="${MSG} -- with customizations" 83 | else 84 | # Assume there will be no customizations. 85 | zope_conf_vanilla=true 86 | for f in etc/zope.conf.d/*.conf ; do 87 | test -f ${f} || continue 88 | # Oh, it looks like there is at least one customization. 89 | if [[ -v zope_conf_vanilla ]] ; then 90 | # Make a note both in the file and in the log. 91 | echo >> etc/${CONF} 92 | echo "# Runtime customizations:" >> etc/${CONF} 93 | MSG="${MSG} -- with customizations" 94 | # We don't need to rerun the same snippet twice here. 95 | unset zope_conf_vanilla 96 | fi 97 | echo >> etc/${CONF} 98 | cat ${f} >> etc/${CONF} 99 | done 100 | fi 101 | 102 | # Handle CORS 103 | $sudo $VENVBIN/python /app/scripts/cors.py 104 | 105 | # Handle ADDONS installation 106 | if [[ -n "${ADDONS}" ]]; then 107 | echo "=======================================================================================" 108 | echo "Installing ADDONS ${ADDONS}" 109 | echo "THIS IS NOT MEANT TO BE USED IN PRODUCTION" 110 | echo "Read about it: https://6.docs.plone.org/install/containers/images/backend.html" 111 | echo "=======================================================================================" 112 | $VENVBIN/pip install ${ADDONS} ${PIP_PARAMS} 113 | fi 114 | 115 | # Handle development addons 116 | if [[ -v DEVELOP ]]; then 117 | echo "=======================================================================================" 118 | echo "Installing DEVELOPment addons ${DEVELOP}" 119 | echo "THIS IS NOT MEANT TO BE USED IN PRODUCTION" 120 | echo "Read about it: https://6.docs.plone.org/install/containers/images/backend.html" 121 | echo "=======================================================================================" 122 | PACKAGES="" 123 | while read -ra DEVADDONS; do 124 | for a in "${DEVADDONS[@]}"; do 125 | PACKAGES+=" --editable ${a}" 126 | done 127 | done <<< "$DEVELOP" 128 | $VENVBIN/pip install ${PACKAGES} ${PIP_PARAMS} 129 | fi 130 | 131 | if [[ "$1" == "start" ]]; then 132 | # Handle Site creation 133 | if [[ -v SITE ]]; then 134 | export TYPE=${TYPE:-volto} 135 | echo "=======================================================================================" 136 | echo "Creating Plone ${TYPE} SITE: ${SITE}" 137 | echo "Aditional profiles: ${PROFILES}" 138 | echo "THIS IS NOT MEANT TO BE USED IN PRODUCTION" 139 | echo "Read about it: https://6.docs.plone.org/install/containers/images/backend.html" 140 | echo "=======================================================================================" 141 | export SITE_ID=${SITE} 142 | $sudo $VENVBIN/zconsole run etc/${CONF} /app/scripts/create_site.py 143 | fi 144 | echo $MSG 145 | if [[ -v LISTEN_PORT ]] ; then 146 | # Ensure the listen port can be set via container --environment. 147 | # Necessary to run multiple backends in a single Podman / Kubernetes pod. 148 | sed -i "s/port = 8080/port = ${LISTEN_PORT}/" etc/zope.ini 149 | fi 150 | exec $sudo $VENVBIN/runwsgi -v etc/zope.ini config_file=${CONF} 151 | elif [[ "$1" == "create-classic" ]]; then 152 | export TYPE=classic 153 | exec $sudo $VENVBIN/zconsole run etc/${CONF} /app/scripts/create_site.py 154 | elif [[ "$1" == "create-volto" ]]; then 155 | export TYPE=volto 156 | exec $sudo $VENVBIN/zconsole run etc/${CONF} /app/scripts/create_site.py 157 | elif [[ "$1" == "create-site" ]]; then 158 | export TYPE=${TYPE:-volto} 159 | exec $sudo $VENVBIN/zconsole run etc/${CONF} /app/scripts/create_site.py 160 | elif [[ "$1" == "console" ]]; then 161 | exec $sudo $VENVBIN/zconsole debug etc/${CONF} 162 | elif [[ "$1" == "run" ]]; then 163 | exec $sudo $VENVBIN/zconsole run etc/${CONF} "${@:2}" 164 | elif [[ "$1" == "addzopeuser" ]]; then 165 | exec $sudo $VENVBIN/addzopeuser -c etc/${CONF} "${@:2}" 166 | elif [[ "$1" == "pack" ]]; then 167 | if [[ -v ZEO_ADDRESS ]]; then 168 | exec $sudo $VENVBIN/zeopack "${@:2}" ${ZEO_ADDRESS} 169 | elif [[ -v RELSTORAGE_DSN ]]; then 170 | exec $sudo $VENVBIN/zodbpack "${@:2}" etc/relstorage_pack.conf 171 | else 172 | exec $sudo $VENVBIN/zconsole run etc/${CONF} /app/scripts/pack.py 173 | fi 174 | else 175 | # Custom 176 | exec "$@" 177 | fi 178 | -------------------------------------------------------------------------------- /skeleton/etc/package-includes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plone/plone-backend/ba1ed09e21936c91bdfc504900bdb752912c999f/skeleton/etc/package-includes/.gitkeep -------------------------------------------------------------------------------- /skeleton/etc/relstorage.conf: -------------------------------------------------------------------------------- 1 | %define INSTANCE /app 2 | instancehome $INSTANCE 3 | 4 | %define CLIENTHOME $(CLIENT_HOME) 5 | clienthome $CLIENTHOME 6 | 7 | debug-mode $(DEBUG_MODE) 8 | security-policy-implementation $(SECURITY_POLICY_IMPLEMENTATION) 9 | verbose-security $(VERBOSE_SECURITY) 10 | default-zpublisher-encoding $(DEFAULT_ZPUBLISHER_ENCODING) 11 | 12 | 13 | CHAMELEON_CACHE $INSTANCE/var/cache 14 | 15 | 16 | 17 | form-memory-limit $(ZOPE_FORM_MEMORY_LIMIT) 18 | 19 | 20 | 21 | # Main database 22 | cache-size $(ZODB_CACHE_SIZE) 23 | %import relstorage 24 | 25 | name $(RELSTORAGE_NAME) 26 | read-only $(RELSTORAGE_READ_ONLY) 27 | keep-history $(RELSTORAGE_KEEP_HISTORY) 28 | commit-lock-timeout $(RELSTORAGE_COMMIT_LOCK_TIMEOUT) 29 | create-schema $(RELSTORAGE_CREATE_SCHEMA) 30 | blob-dir $INSTANCE/var/blobstorage 31 | shared-blob-dir $(RELSTORAGE_SHARED_BLOB_DIR) 32 | blob-cache-size $(RELSTORAGE_BLOB_CACHE_SIZE) 33 | blob-cache-size-check $(RELSTORAGE_BLOB_CACHE_SIZE_CHECK) 34 | blob-cache-size-check-external $(RELSTORAGE_BLOB_CACHE_SIZE_CHECK_EXTERNAL) 35 | blob-chunk-size $(RELSTORAGE_BLOB_CHUNK_SIZE) 36 | 37 | dsn $(RELSTORAGE_DSN) 38 | 39 | 40 | mount-point / 41 | 42 | -------------------------------------------------------------------------------- /skeleton/etc/relstorage_pack.conf: -------------------------------------------------------------------------------- 1 | 2 | pack-gc true 3 | 4 | dsn $(RELSTORAGE_DSN) 5 | 6 | 7 | -------------------------------------------------------------------------------- /skeleton/etc/site.zcml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /skeleton/etc/zeo.conf: -------------------------------------------------------------------------------- 1 | %define INSTANCE /app 2 | instancehome $INSTANCE 3 | 4 | %define CLIENTHOME $(CLIENT_HOME) 5 | clienthome $CLIENTHOME 6 | 7 | debug-mode $(DEBUG_MODE) 8 | security-policy-implementation $(SECURITY_POLICY_IMPLEMENTATION) 9 | verbose-security $(VERBOSE_SECURITY) 10 | default-zpublisher-encoding $(DEFAULT_ZPUBLISHER_ENCODING) 11 | 12 | 13 | CHAMELEON_CACHE $INSTANCE/var/cache 14 | 15 | 16 | 17 | form-memory-limit $(ZOPE_FORM_MEMORY_LIMIT) 18 | 19 | 20 | 21 | # Main database 22 | cache-size $(ZODB_CACHE_SIZE) 23 | 24 | name zeostorage 25 | var $INSTANCE/var 26 | blob-dir $INSTANCE/var/blobstorage 27 | read-only $(ZEO_READ_ONLY) 28 | read-only-fallback $(ZEO_CLIENT_READ_ONLY_FALLBACK) 29 | shared-blob-dir $(ZEO_SHARED_BLOB_DIR) 30 | server $(ZEO_ADDRESS) 31 | storage $(ZEO_STORAGE) 32 | cache-size $(ZEO_CLIENT_CACHE_SIZE) 33 | drop-cache-rather-verify $(ZEO_DROP_CACHE_RATHER_VERIFY) 34 | 35 | mount-point / 36 | 37 | -------------------------------------------------------------------------------- /skeleton/etc/zope.conf: -------------------------------------------------------------------------------- 1 | %define INSTANCE /app 2 | instancehome $INSTANCE 3 | 4 | %define CLIENTHOME $(CLIENT_HOME) 5 | clienthome $CLIENTHOME 6 | 7 | debug-mode $(DEBUG_MODE) 8 | security-policy-implementation $(SECURITY_POLICY_IMPLEMENTATION) 9 | verbose-security $(VERBOSE_SECURITY) 10 | default-zpublisher-encoding $(DEFAULT_ZPUBLISHER_ENCODING) 11 | 12 | 13 | CHAMELEON_CACHE $INSTANCE/var/cache 14 | 15 | 16 | 17 | form-memory-limit $(ZOPE_FORM_MEMORY_LIMIT) 18 | form-disk-limit $(ZOPE_FORM_DISK_LIMIT) 19 | form-memfile-limit $(ZOPE_FORM_MEMFILE_LIMIT) 20 | 21 | 22 | 23 | # Main database 24 | cache-size $(ZODB_CACHE_SIZE) 25 | # Blob-enabled FileStorage database 26 | 27 | blob-dir $INSTANCE/var/blobstorage 28 | # FileStorage database 29 | 30 | path $INSTANCE/var/filestorage/Data.fs 31 | 32 | 33 | mount-point / 34 | 35 | -------------------------------------------------------------------------------- /skeleton/etc/zope.conf.d/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plone/plone-backend/ba1ed09e21936c91bdfc504900bdb752912c999f/skeleton/etc/zope.conf.d/.gitkeep -------------------------------------------------------------------------------- /skeleton/etc/zope.ini: -------------------------------------------------------------------------------- 1 | [app:zope] 2 | use = egg:Zope#main 3 | zope_conf = %(here)s/%(config_file)s 4 | 5 | [server:main] 6 | use = egg:waitress#main 7 | host = 0.0.0.0 8 | port = 8080 9 | threads = 2 10 | clear_untrusted_proxy_headers = false 11 | max_request_body_size = 1073741824 12 | 13 | 14 | [filter:translogger] 15 | use = egg:Paste#translogger 16 | setup_console_handler = False 17 | 18 | [pipeline:main] 19 | pipeline = 20 | egg:Zope#httpexceptions 21 | translogger 22 | zope 23 | 24 | [loggers] 25 | keys = root, waitress.queue, waitress, wsgi 26 | 27 | [handlers] 28 | keys = accesslog, eventlog 29 | 30 | [formatters] 31 | keys = generic, message 32 | 33 | [formatter_generic] 34 | format = %(asctime)s %(levelname)s [%(name)s:%(lineno)s][%(threadName)s] %(message)s 35 | datefmt = %Y-%m-%d %H:%M:%S 36 | 37 | [formatter_message] 38 | format = %(message)s 39 | 40 | [logger_root] 41 | level = INFO 42 | handlers = eventlog 43 | 44 | [logger_waitress.queue] 45 | level = INFO 46 | handlers = eventlog 47 | qualname = waitress.queue 48 | propagate = 0 49 | 50 | [logger_waitress] 51 | level = INFO 52 | handlers = eventlog 53 | qualname = waitress 54 | propagate = 0 55 | 56 | [logger_wsgi] 57 | level = WARN 58 | handlers = accesslog 59 | qualname = wsgi 60 | propagate = 0 61 | 62 | [handler_accesslog] 63 | class = StreamHandler 64 | args = (sys.stdout,) 65 | level = INFO 66 | formatter = message 67 | 68 | [handler_eventlog] 69 | class = StreamHandler 70 | args = (sys.stderr,) 71 | level = INFO 72 | formatter = generic 73 | -------------------------------------------------------------------------------- /skeleton/inituser: -------------------------------------------------------------------------------- 1 | admin:{SHA}0DPiKuNIrrVmD8IUCuw1hQxNqZc= -------------------------------------------------------------------------------- /skeleton/scripts/cors.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | CORS_TEMPLATE = """ 5 | 8 | 16 | 17 | 18 | """ 19 | 20 | def main(conf="/app/etc/package-includes/999-cors-overrides.zcml"): 21 | """ Configure CORS Policies 22 | """ 23 | if not [e for e in os.environ if e.startswith("CORS_")]: 24 | return 25 | 26 | allow_origin = os.environ.get("CORS_ALLOW_ORIGIN", "http://localhost:3000,http://127.0.0.1:3000") 27 | allow_methods = os.environ.get("CORS_ALLOW_METHODS", "DELETE,GET,OPTIONS,PATCH,POST,PUT") 28 | allow_credentials = os.environ.get("CORS_ALLOW_CREDENTIALS", "true") 29 | expose_headers = os.environ.get("CORS_EXPOSE_HEADERS", "Content-Length,X-My-Header") 30 | allow_headers = os.environ.get("CORS_ALLOW_HEADERS", "Accept,Authorization,Content-Type,X-Custom-Header,Lock-Token") 31 | max_age = os.environ.get("CORS_MAX_AGE", "3600") 32 | cors_conf = CORS_TEMPLATE.format( 33 | allow_origin=allow_origin, 34 | allow_methods=allow_methods, 35 | allow_credentials=allow_credentials, 36 | expose_headers=expose_headers, 37 | allow_headers=allow_headers, 38 | max_age=max_age 39 | ) 40 | with open(conf, "w") as cors_file: 41 | cors_file.write(cors_conf) 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /skeleton/scripts/create_site.py: -------------------------------------------------------------------------------- 1 | from AccessControl.SecurityManagement import newSecurityManager 2 | from pathlib import Path 3 | from plone.distribution.api import site as site_api 4 | from Testing.makerequest import makerequest 5 | 6 | import json 7 | import logging 8 | import os 9 | import transaction 10 | 11 | 12 | logging.basicConfig(format="%(message)s") 13 | 14 | # Silence some loggers 15 | for logger_name in [ 16 | "GenericSetup.componentregistry", 17 | "Products.MimetypesRegistry.MimeTypesRegistry", 18 | ]: 19 | logging.getLogger(logger_name).setLevel(logging.ERROR) 20 | 21 | logger = logging.getLogger("Plone Site Creation") 22 | logger.setLevel(logging.DEBUG) 23 | 24 | SCRIPT_DIR = Path().cwd() / "scripts" 25 | 26 | truthy = frozenset(("t", "true", "y", "yes", "on", "1")) 27 | 28 | 29 | def asbool(s): 30 | """Return the boolean value ``True`` if the case-lowered value of string 31 | input ``s`` is a :term:`truthy string`. If ``s`` is already one of the 32 | boolean values ``True`` or ``False``, return it.""" 33 | if s is None: 34 | return False 35 | if isinstance(s, bool): 36 | return s 37 | s = str(s).strip() 38 | return s.lower() in truthy 39 | 40 | 41 | app = makerequest(globals()["app"]) 42 | 43 | request = app.REQUEST 44 | 45 | admin = app.acl_users.getUserById("admin") 46 | admin = admin.__of__(app.acl_users) 47 | newSecurityManager(None, admin) 48 | 49 | # VARS 50 | ANSWERS = os.getenv("ANSWERS", "default") 51 | DELETE_EXISTING = asbool(os.getenv("DELETE_EXISTING")) 52 | DISTRIBUTION = os.getenv("DISTRIBUTION", "") 53 | 54 | if not DISTRIBUTION: 55 | # We used to support setting TYPE 'volto' or 'classic'. 56 | TYPE = os.getenv("TYPE", "") 57 | if TYPE == "classic": 58 | DISTRIBUTION = "classic" 59 | elif TYPE == "volto": 60 | DISTRIBUTION = "volto" 61 | 62 | # Load default site creation parameters 63 | answers_file = SCRIPT_DIR / f"{ANSWERS}.json" 64 | answers = json.loads(answers_file.read_text()) 65 | 66 | # Override the defaults from the OS environment 67 | if DISTRIBUTION: 68 | answers["distribution"] = DISTRIBUTION 69 | SITE_ID = os.getenv("SITE_ID") 70 | if SITE_ID: 71 | answers["site_id"] = SITE_ID 72 | SITE_DEFAULT_LANGUAGE = os.getenv("SITE_DEFAULT_LANGUAGE") 73 | if SITE_DEFAULT_LANGUAGE: 74 | answers["default_language"] = SITE_DEFAULT_LANGUAGE 75 | SETUP_CONTENT = os.getenv("SETUP_CONTENT") 76 | if SETUP_CONTENT is not None: 77 | answers["setup_content"] = asbool(SETUP_CONTENT) 78 | TIMEZONE = os.getenv("TIMEZONE") 79 | if TIMEZONE: 80 | answers["portal_timezone"] = TIMEZONE 81 | ADDITIONAL_PROFILES = os.getenv("PROFILES", os.getenv("ADDITIONAL_PROFILES", "")) 82 | additional_profiles = [] 83 | if ADDITIONAL_PROFILES: 84 | additional_profiles = [profile.strip() for profile in ADDITIONAL_PROFILES.split(",")] 85 | 86 | # Get the final site_id and distribution from the updated answers. 87 | site_id = answers["site_id"] 88 | DISTRIBUTION = answers["distribution"] 89 | logger.info(f"Creating a new Plone site @ {site_id}") 90 | logger.info(f" - Using the {DISTRIBUTION} distribution and answers from {answers_file}") 91 | 92 | 93 | if site_id in app.objectIds() and DELETE_EXISTING: 94 | app.manage_delObjects([site_id]) 95 | transaction.commit() 96 | app._p_jar.sync() 97 | logger.info(f" - Deleted existing site with id {site_id}") 98 | else: 99 | logger.info( 100 | f" - Stopping site creation, as there is already a site with id {site_id}. " 101 | "Set DELETE_EXISTING=1 to delete the existing site before creating a new one." 102 | ) 103 | 104 | if site_id not in app.objectIds(): 105 | site = site_api._create_site( 106 | context=app, distribution_name=DISTRIBUTION, answers=answers 107 | ) 108 | transaction.commit() 109 | app._p_jar.sync() 110 | logger.info(" - Site created!") 111 | 112 | if additional_profiles: 113 | for profile_id in additional_profiles: 114 | logger.info(f" - Importing profile {profile_id}") 115 | site.portal_setup.runAllImportStepsFromProfile(f"profile-{profile_id}") 116 | transaction.commit() 117 | app._p_jar.sync() 118 | -------------------------------------------------------------------------------- /skeleton/scripts/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "site_id": "Plone", 3 | "title": "Welcome to Plone 6", 4 | "description": "Site created with a new Plone Distribution", 5 | "default_language": "en", 6 | "portal_timezone": "Europe/Berlin", 7 | "setup_content": true 8 | } -------------------------------------------------------------------------------- /skeleton/scripts/pack.py: -------------------------------------------------------------------------------- 1 | from Zope2 import DB 2 | DB.pack() 3 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Test Suite 2 | 3 | ## Running Tests 4 | 5 | ```console 6 | $ ./run.sh 7 | 8 | usage: run.sh [-t test ...] image:tag [...] 9 | ie: run.sh plone/plone-backend:6.0.0a1 10 | run.sh -t utc plone/plone-backend:6.0.0a1 11 | 12 | This script processes the specified Docker images to test their running 13 | environments. 14 | ``` 15 | 16 | Run all the tests that are applicable to the `plone/plone-backend:6.0.0a1` image: 17 | 18 | ```console 19 | $ ./run.sh plone/plone-backend:6.0.0a1 20 | testing plone/plone-backend:6.0.0a1 21 | 'utc' [1/4]...passed 22 | 'no-hard-coded-passwords' [2/4]...passed 23 | 'override-cmd' [3/4]...passed 24 | ``` 25 | 26 | ## Writing Tests 27 | 28 | ### `tests//` 29 | 30 | Each test lives in a separate directory inside `tests/`, and `config.sh` determines which images each test applies to. 31 | 32 | #### `tests//run.sh` 33 | 34 | This is the actual "test script". In the script, `$1` is the name of the image we're testing. An exit code of zero indicates success/passing, and a non-zero exit code indicates failure. 35 | 36 | For example, `tests/utc/run.sh` consists of: 37 | 38 | ```bash 39 | #!/bin/bash 40 | set -e 41 | 42 | docker run --rm --entrypoint date "$1" +%Z 43 | ``` 44 | 45 | Which runs `date` inside the given image, and outputs the current timezone according to `date +%Z`, which should be `UTC`. See `tests//expected-std-out.txt` below for the other half of this test. 46 | 47 | There are several pre-written `run.sh` scripts to help with common tasks. See the `tests/run-*-in-container.sh` section below for more details about these. 48 | 49 | #### `tests//expected-std-out.txt` 50 | 51 | If this file exists, then this test's `run.sh` is expected to output exactly the contents of this file on standard output, and differences will be assumed to denote test failure. 52 | 53 | To continue with our `utc` test example, its contents in this file are simply: 54 | 55 | UTC 56 | 57 | If a test fails due to having incorrect output, the `diff` between the generated output and the expected output will be displayed: 58 | 59 | ```console 60 | $ ./run.sh -t utc plone/plone-backend:6.0.0a1 61 | testing plone/plone-backend:6.0.0a1 62 | 'utc' [1/1]...failed; unexpected output: 63 | --- tests/utc/expected-std-out.txt 2015-02-05 16:52:05.013273118 -0700 64 | +++ - 2015-03-13 15:11:49.725116569 -0600 65 | @@ -1 +1 @@ 66 | -UTC 67 | +EDT 68 | ``` 69 | 70 | (this is an example of `plone/plone-backend:6.0.0a1` failing the `utc` test) 71 | 72 | ## Configuring the Test Suite 73 | 74 | ### `config.sh` 75 | 76 | This file controls which tests apply (or don't apply) to each image. 77 | 78 | When testing an image, the list of tests to apply are calculated by doing `globalTests + imageTests[testAlias[image]] + imageTests[image]`. Any tests for which `globalExcludeTests[image_test]` is set are removed. If `-t` is specified one or more times, any tests not specified explicitly via `-t` are also removed. 79 | 80 | #### `globalTests=( test ... )` 81 | 82 | This list of tests applies to every image minus combinations listed in `globalExcludeTests` (such as `hello-world` not getting the `utc` test, since it has no `date` binary in order for the test to work). 83 | 84 | ```bash 85 | globalTests+=( 86 | utc 87 | no-hard-coded-passwords 88 | ) 89 | ``` 90 | 91 | #### `testAlias=( [image]='image' ... )` 92 | 93 | This array defines image aliases 94 | 95 | #### `imageTests=( [image]='test ...' ... )` 96 | 97 | This array defines the list of explicit tests for each image. For example, the `zeo` image has a test which verifies that basic functionality works, such as starting up the image and connecting to it from a separate container. 98 | 99 | #### `globalExcludeTests=( [image_test]=1 ... )` 100 | 101 | Defines image+test combinations which shouldn't ever run (usually because they won't work, like trying to run `date` in the `hello-world` image for the `utc` test, which has only one binary). 102 | 103 | ```bash 104 | globalExcludeTests+=( 105 | # single-binary images 106 | [hello-world_utc]=1 107 | ) 108 | ``` 109 | 110 | ### Alternate config files 111 | 112 | If you would like to run the Official Image tests against your own images, you can use the `-c/--config` flag to specify one or more alternate config files. These config files should configure the same environment variables used by the default `config.sh` (see above). 113 | 114 | ```bash 115 | imageTests+=( 116 | [myorg/myimage]=' 117 | my-custom-test 118 | ' 119 | ) 120 | ``` 121 | 122 | **Note**: If you do use your own config file, the `config.sh` included here will no longer be loaded by default. If you want to load it in addition to your own config file (for example, to run the `globalTests` against your own image), use an additional `--config` flag. 123 | 124 | ```console 125 | $ /path/to/official-images/test/run.sh --config /path/to/official-images/test/config.sh --config ./my-config.sh myimage 126 | ``` 127 | -------------------------------------------------------------------------------- /test/config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | globalTests+=( 3 | utc 4 | no-hard-coded-passwords 5 | override-cmd 6 | ) 7 | 8 | imageTests_main_set=' 9 | plone-basics 10 | plone-develop 11 | plone-site 12 | plone-addons 13 | plone-cors 14 | plone-arbitrary-user 15 | plone-listenport 16 | plone-zeoclient 17 | plone-relstorage 18 | plone-shared-blob-dir 19 | ' 20 | 21 | imageTests+=( 22 | [plone/plone-backend]=$imageTests_main_set 23 | [ghcr.io/plone/plone-backend]=$imageTests_main_set 24 | ) 25 | 26 | globalExcludeTests+=( 27 | 28 | ) 29 | -------------------------------------------------------------------------------- /test/retry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | opts="$(getopt -o 'i:c:t:s:' --long 'image:,cid:,tries:,sleep:' -- "$@")" 6 | eval set -- "$opts" 7 | while true; do 8 | flag=$1 9 | shift 10 | case "$flag" in 11 | --image|-i) image="$1" && shift ;; 12 | --cid|-c) cid="$1" && shift ;; 13 | --tries|-t) tries="$1" && shift ;; 14 | --sleep|-s) sleep="$1" && shift ;; 15 | --) break;; 16 | esac 17 | done 18 | 19 | if [ $# -eq 0 ]; then 20 | echo >&2 'retry.sh requires a command to run' 21 | false 22 | fi 23 | 24 | : ${tries:=10} 25 | : ${sleep:=2} 26 | 27 | while ! eval "$@" &> /dev/null; do 28 | (( tries-- )) 29 | if [ $tries -le 0 ]; then 30 | echo >&2 "${image:-the container} failed to accept connections in a reasonable amount of time!" 31 | [ "$cid" ] && ( set -x && docker logs "$cid" ) >&2 || true 32 | ( set -x && eval "$@" ) >&2 || true # to hopefully get a useful error message 33 | false 34 | fi 35 | if [ "$cid" ] && [ "$(docker inspect -f '{{.State.Running}}' "$cid" 2>/dev/null)" != 'true' ]; then 36 | echo >&2 "${image:-the container} stopped unexpectedly!" 37 | ( set -x && docker logs "$cid" ) >&2 || true 38 | false 39 | fi 40 | echo >&2 -n . 41 | sleep "$sleep" 42 | done 43 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -Eeuo pipefail 3 | 4 | dir="$(dirname "$(readlink -f "$BASH_SOURCE")")" 5 | 6 | self="$(basename "$0")" 7 | 8 | usage() { 9 | cat <&2 && false; })" 23 | eval set -- "$opts" 24 | 25 | declare -A argTests=() 26 | declare -a configs=() 27 | dryRun= 28 | keepNamespace= 29 | while true; do 30 | flag=$1 31 | shift 32 | case "$flag" in 33 | --dry-run) dryRun=1 ;; 34 | --help|-h|'-?') usage && exit 0 ;; 35 | --test|-t) argTests["$1"]=1 && shift ;; 36 | --config|-c) configs+=("$(readlink -f "$1")") && shift ;; 37 | --keep-namespace) keepNamespace=1 ;; 38 | --) break ;; 39 | *) 40 | { 41 | echo "error: unknown flag: $flag" 42 | usage 43 | } >&2 44 | exit 1 45 | ;; 46 | esac 47 | done 48 | 49 | if [ "$#" -eq 0 ]; then 50 | usage >&2 51 | exit 1 52 | fi 53 | 54 | # declare configuration variables 55 | declare -a globalTests=() 56 | declare -A testAlias=() 57 | declare -A imageTests=() 58 | declare -A globalExcludeTests=() 59 | declare -A explicitTests=() 60 | 61 | # if there are no user-specified configs, use the default config 62 | if [ "${#configs[@]}" -eq 0 ]; then 63 | configs+=( "$dir/config.sh" ) 64 | fi 65 | 66 | case "$(uname -o)" in 67 | Msys) 68 | # https://stackoverflow.com/a/34386471/433558 69 | # https://github.com/docker/toolbox/blob/6960f28d5b01857d69b2095a02949264f09d3e57/windows/start.sh#L104-L107 70 | docker() { 71 | MSYS_NO_PATHCONV=1 command docker "$@" 72 | } 73 | export -f docker 74 | ;; 75 | esac 76 | 77 | # load the configs 78 | declare -A testPaths=() 79 | for conf in "${configs[@]}"; do 80 | . "$conf" 81 | 82 | # Determine the full path to any newly-declared tests 83 | confDir="$(dirname "$conf")" 84 | 85 | for testName in ${globalTests[@]} ${imageTests[@]}; do 86 | [ -n "${testPaths[$testName]:-}" ] && continue 87 | 88 | if [ -d "$confDir/tests/$testName" ]; then 89 | # Test directory found relative to the conf file 90 | testPaths[$testName]="$confDir/tests/$testName" 91 | elif [ -d "$dir/tests/$testName" ]; then 92 | # Test directory found in the main tests/ directory 93 | testPaths[$testName]="$dir/tests/$testName" 94 | fi 95 | done 96 | done 97 | 98 | didFail= 99 | for dockerImage in "$@"; do 100 | echo "testing $dockerImage" 101 | 102 | repo="${dockerImage%:*}" 103 | tagVar="${dockerImage#*:}" 104 | #version="${tagVar%-*}" 105 | variant="${tagVar##*-}" 106 | 107 | case "$tagVar" in 108 | *onbuild*) 109 | # "maven:onbuild-alpine" is still onbuild 110 | # ONCE ONBUILD, ALWAYS ONBUILD 111 | variant='onbuild' 112 | ;; 113 | *apache-*) 114 | # lolPHP 115 | variant='apache' 116 | ;; 117 | *fpm-*) 118 | # lolPHP 119 | variant='fpm' 120 | ;; 121 | *alpine*) 122 | # "alpine3.4", "alpine3.6", "nginx:alpine-perl", etc are still "alpine" variants 123 | variant='alpine' 124 | ;; 125 | slim-*|*-slim-*) 126 | # "slim-jessie" is still "slim" 127 | variant='slim' 128 | ;; 129 | psmdb-*) 130 | # Percona Server for MongoDB is still "mongo" 131 | variant='psmdb' 132 | ;; 133 | *nanoserver*) 134 | # all nanoserver variants are windows 135 | variant='nanoserver' 136 | ;; 137 | *windowsservercore*) 138 | # all servercore variants are windows 139 | variant='windowsservercore' 140 | ;; 141 | esac 142 | 143 | testRepo="$repo" 144 | if [ -z "$keepNamespace" ]; then 145 | testRepo="${testRepo##*/}" 146 | fi 147 | 148 | for possibleAlias in \ 149 | "${testAlias[$repo:$variant]:-}" \ 150 | "${testAlias[$repo]:-}" \ 151 | "${testAlias[$testRepo:$variant]:-}" \ 152 | "${testAlias[$testRepo]:-}" \ 153 | ; do 154 | if [ -n "$possibleAlias" ]; then 155 | testRepo="$possibleAlias" 156 | break 157 | fi 158 | done 159 | 160 | explicitVariant= 161 | for possibleExplicit in \ 162 | "${explicitTests[:$variant]:-}" \ 163 | "${explicitTests[$repo:$variant]:-}" \ 164 | "${explicitTests[$repo]:-}" \ 165 | "${explicitTests[$testRepo:$variant]:-}" \ 166 | "${explicitTests[$testRepo]:-}" \ 167 | ; do 168 | if [ -n "$possibleExplicit" ]; then 169 | explicitVariant=1 170 | break 171 | fi 172 | done 173 | 174 | testCandidates=() 175 | if [ -z "$explicitVariant" ]; then 176 | testCandidates+=( "${globalTests[@]}" ) 177 | fi 178 | testCandidates+=( 179 | ${imageTests[:$variant]:-} 180 | ) 181 | if [ -z "$explicitVariant" ]; then 182 | testCandidates+=( 183 | ${imageTests[$testRepo]:-} 184 | ) 185 | fi 186 | testCandidates+=( 187 | ${imageTests[$testRepo:$variant]:-} 188 | ) 189 | if [ "$testRepo" != "$repo" ]; then 190 | if [ -z "$explicitVariant" ]; then 191 | testCandidates+=( 192 | ${imageTests[$repo]:-} 193 | ) 194 | fi 195 | testCandidates+=( 196 | ${imageTests[$repo:$variant]:-} 197 | ) 198 | fi 199 | 200 | tests=() 201 | for t in "${testCandidates[@]}"; do 202 | if [ "${#argTests[@]}" -gt 0 ] && [ -z "${argTests[$t]:-}" ]; then 203 | # skipping due to -t 204 | continue 205 | fi 206 | 207 | if [ \ 208 | ! -z "${globalExcludeTests[${testRepo}_$t]:-}" \ 209 | -o ! -z "${globalExcludeTests[${testRepo}:${variant}_$t]:-}" \ 210 | -o ! -z "${globalExcludeTests[:${variant}_$t]:-}" \ 211 | -o ! -z "${globalExcludeTests[${repo}_$t]:-}" \ 212 | -o ! -z "${globalExcludeTests[${repo}:${variant}_$t]:-}" \ 213 | -o ! -z "${globalExcludeTests[:${variant}_$t]:-}" \ 214 | ]; then 215 | # skipping due to exclude 216 | continue 217 | fi 218 | 219 | tests+=( "$t" ) 220 | done 221 | 222 | # check for zero tests before checking for existing image 223 | # this will make windows variants no longer fail 224 | if [ "${#tests[@]}" -eq '0' ]; then 225 | echo $'\timage has no tests...skipping' 226 | continue 227 | fi 228 | 229 | if ! docker inspect "$dockerImage" &> /dev/null; then 230 | echo $'\timage does not exist!' 231 | didFail=1 232 | continue 233 | fi 234 | 235 | currentTest=0 236 | totalTest="${#tests[@]}" 237 | for t in "${tests[@]}"; do 238 | (( currentTest+=1 )) 239 | echo -ne "\t'$t' [$currentTest/$totalTest]..." 240 | 241 | # run test against dockerImage here 242 | # find the script for the test 243 | scriptDir="${testPaths[$t]}" 244 | if [ -d "$scriptDir" ]; then 245 | script="$scriptDir/run.sh" 246 | if [ -x "$script" ] && [ ! -d "$script" ]; then 247 | # TODO dryRun logic 248 | if output="$("$script" "$dockerImage")"; then 249 | output="$(tr -d '\r' <<<"$output")" # Windows gives us \r\n ... :D 250 | if [ -f "$scriptDir/expected-std-out.txt" ] && ! d="$(diff -u "$scriptDir/expected-std-out.txt" - <<<"$output" 2>/dev/null)"; then 251 | echo 'failed; unexpected output:' 252 | echo "$d" 253 | didFail=1 254 | else 255 | echo 'passed' 256 | fi 257 | else 258 | echo 'failed' 259 | didFail=1 260 | fi 261 | else 262 | echo "skipping" 263 | echo >&2 "error: $script missing, not executable or is a directory" 264 | didFail=1 265 | continue 266 | fi 267 | else 268 | echo "skipping" 269 | echo >&2 "error: unable to locate test '$t'" 270 | didFail=1 271 | continue 272 | fi 273 | done 274 | done 275 | 276 | if [ -n "$didFail" ]; then 277 | exit 1 278 | fi 279 | -------------------------------------------------------------------------------- /test/tests/no-hard-coded-passwords/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | IFS=$'\n' 5 | userPasswds=( $(docker run --rm --user 0:0 --entrypoint cut "$1" -d: -f1-2 /etc/passwd) ) 6 | userShadows=() 7 | if grep -qE ':x$' <<<"${userPasswds[*]}"; then 8 | userShadows=( $(docker run --rm --user 0:0 --entrypoint cut "$1" -d: -f1-2 /etc/shadow || true) ) 9 | fi 10 | unset IFS 11 | 12 | declare -A passwds=() 13 | for userPasswd in "${userPasswds[@]}"; do 14 | user="${userPasswd%%:*}" 15 | pass="${userPasswd#*:}" 16 | passwds[$user]="$pass" 17 | done 18 | for userShadow in "${userShadows[@]}"; do 19 | user="${userShadow%%:*}" 20 | if [ "${passwds[$user]}" = 'x' ]; then 21 | pass="${userShadow#*:}" 22 | passwds[$user]="$pass" 23 | fi 24 | done 25 | 26 | ret=0 27 | for user in "${!passwds[@]}"; do 28 | pass="${passwds[$user]}" 29 | 30 | if [ -z "$pass" ]; then 31 | # for root this is a security vulnerability (see CVE-2019-5021, for example) 32 | if [ "$user" = 'root' ]; then 33 | echo >&2 "error: empty password detected for '$user'" 34 | ret=1 35 | else 36 | echo >&2 "warning: empty password detected for '$user'" 37 | fi 38 | continue 39 | fi 40 | 41 | if [ "$pass" = '*' ]; then 42 | # "If the password field contains some string that is not a valid result of crypt(3), for instance ! or *, the user will not be able to use a unix password to log in (but the user may log in the system by other means)." 43 | # (Debian uses this for having default-provided accounts without a password but also without being explicitly locked, for example) 44 | continue 45 | fi 46 | 47 | if [ "${pass:0:1}" = '!' ]; then 48 | # '!anything' means "locked" password 49 | #echo >&2 "warning: locked password detected for '$user': '$pass'" 50 | continue 51 | fi 52 | 53 | if [ "${pass:0:1}" = '$' ]; then 54 | # gotta be crypt ($id$salt$encrypted), must be a fail 55 | if [[ "$1" == cirros* ]] && [ "$user" = 'cirros' ]; then 56 | # cirros is "supposed" to have a password for the cirros user 57 | # https://github.com/cirros-dev/cirros/tree/68771c7620ec100db4afb75dc4c145f4e49fe7fc#readme 58 | echo >&2 "warning: CirrOS has a password for the 'cirros' user (as intended)" 59 | continue 60 | fi 61 | echo >&2 "error: crypt password detected for '$user': '$pass'" 62 | ret=1 63 | continue 64 | fi 65 | 66 | echo >&2 "warning: garbage password detected for '$user': '$pass'" 67 | done 68 | 69 | exit "$ret" 70 | -------------------------------------------------------------------------------- /test/tests/override-cmd/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -Eeuo pipefail 3 | 4 | image="$1" 5 | 6 | # test that we can override the CMD with echo 7 | # https://github.com/docker-library/official-images/blob/d28cb89e79417cac50c2a8ae163a9b3b79167f79/README.md#consistency 8 | 9 | hello="world-$RANDOM-$RANDOM" 10 | 11 | cmd=( echo "Hello $hello" ) 12 | case "$image" in 13 | *windowsservercore* | *nanoserver*) 14 | cmd=( cmd /Q /S /C "${cmd[*]}" ) 15 | ;; 16 | esac 17 | 18 | # test first with --entrypoint to verify that we even have echo (tests for single-binary images FROM scratch, essentially) 19 | if ! testOutput="$(docker run --rm --entrypoint "${cmd[0]}" "$image" "${cmd[@]:1}" 2>/dev/null)"; then 20 | echo >&2 'image does not appear to contain "echo" -- assuming single-binary image' 21 | exit 22 | fi 23 | testOutput="$(tr -d '\r' <<<"$testOutput")" # Windows gives us \r\n ... :D 24 | [ "$testOutput" = "Hello $hello" ] 25 | 26 | # now test with normal command to verify the default entrypoint is OK 27 | output="$(docker run --rm "$image" "${cmd[@]}")" 28 | output="$(tr -d '\r' <<<"$output")" # Windows gives us \r\n ... :D 29 | [ "$output" = "Hello $hello" ] 30 | -------------------------------------------------------------------------------- /test/tests/plone-addons/expected-std-out.txt: -------------------------------------------------------------------------------- 1 | ======================================================================================= 2 | Installing ADDONS eea.facetednavigation 3 | THIS IS NOT MEANT TO BE USED IN PRODUCTION 4 | Read about it: https://6.docs.plone.org/install/containers/images/backend.html 5 | ======================================================================================= 6 | eea.facetednavigation 7 | -------------------------------------------------------------------------------- /test/tests/plone-addons/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | docker run -i --rm \ 5 | -e ADDONS="eea.facetednavigation" \ 6 | -e PIP_PARAMS="-q --disable-pip-version-check" \ 7 | "$1" bin/python -c "from eea import facetednavigation; print(facetednavigation.__name__)" 8 | -------------------------------------------------------------------------------- /test/tests/plone-arbitrary-user/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | dir="$(dirname "$(readlink -f "$BASH_SOURCE")")" 5 | 6 | image="$1" 7 | 8 | PLONE_TEST_SLEEP=3 9 | PLONE_TEST_TRIES=5 10 | 11 | vname="/tmp/plone-data-$RANDOM-$RANDOM" 12 | cname="plone-container-$RANDOM-$RANDOM" 13 | vid="$(mkdir -p $vname)" 14 | cid="$(docker run -d --user=$(id -u) -v $vname:/data --name $cname $image)" 15 | trap "docker rm -vf $cid > /dev/null; rm -rf $vname > /dev/null" EXIT 16 | 17 | get() { 18 | docker run --rm -i \ 19 | --link "$cname":plone \ 20 | --entrypoint /app/bin/python \ 21 | "$image" \ 22 | -c "from urllib.request import urlopen; con = urlopen('$1'); print(con.read())" 23 | } 24 | 25 | . "$dir/../../retry.sh" --tries "$PLONE_TEST_TRIES" --sleep "$PLONE_TEST_SLEEP" get "http://plone:8080" 26 | 27 | # Plone is up and running 28 | [[ "$(get 'http://plone:8080')" == *"Welcome to Plone!"* ]] 29 | -------------------------------------------------------------------------------- /test/tests/plone-basics/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | dir="$(dirname "$(readlink -f "$BASH_SOURCE")")" 5 | 6 | image="$1" 7 | 8 | PLONE_TEST_SLEEP=3 9 | PLONE_TEST_TRIES=5 10 | 11 | cname="plone-container-$RANDOM-$RANDOM" 12 | cid="$(docker run -d --name "$cname" "$image")" 13 | trap "docker rm -vf $cid > /dev/null" EXIT 14 | 15 | get() { 16 | docker run --rm -i \ 17 | --link "$cname":plone \ 18 | --entrypoint /app/bin/python \ 19 | "$image" \ 20 | -c "from urllib.request import urlopen; con = urlopen('$1'); print(con.read())" 21 | } 22 | 23 | . "$dir/../../retry.sh" --tries "$PLONE_TEST_TRIES" --sleep "$PLONE_TEST_SLEEP" get "http://plone:8080" 24 | 25 | # Plone is up and running 26 | [[ "$(get 'http://plone:8080')" == *"Welcome to Plone!"* ]] 27 | -------------------------------------------------------------------------------- /test/tests/plone-cors/expected-std-out.txt: -------------------------------------------------------------------------------- 1 | 3 | 6 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/tests/plone-cors/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | docker run -i --rm \ 5 | -e CORS_ALLOW_ORIGIN="www.example.com" \ 6 | "$1" cat /app/etc/package-includes/999-cors-overrides.zcml 7 | -------------------------------------------------------------------------------- /test/tests/plone-develop/expected-std-out.txt: -------------------------------------------------------------------------------- 1 | ======================================================================================= 2 | Installing DEVELOPment addons /app/src/helloworld 3 | THIS IS NOT MEANT TO BE USED IN PRODUCTION 4 | Read about it: https://6.docs.plone.org/install/containers/images/backend.html 5 | ======================================================================================= 6 | Hello World! 7 | -------------------------------------------------------------------------------- /test/tests/plone-develop/helloworld/helloworld/__init__.py: -------------------------------------------------------------------------------- 1 | def say_hi(): 2 | print('Hello World!') 3 | -------------------------------------------------------------------------------- /test/tests/plone-develop/helloworld/setup.py: -------------------------------------------------------------------------------- 1 | """ EEA Faceted Navigation Installer 2 | """ 3 | import os 4 | from os.path import join 5 | from setuptools import setup, find_packages 6 | 7 | setup(name="helloworld", 8 | version="0.1", 9 | description="Hello World", 10 | author='Plone', 11 | url='https://github.com/plone/plone-backend', 12 | license='GPL version 2', 13 | packages=['helloworld'], 14 | include_package_data=True, 15 | zip_safe=False, 16 | install_requires=[ 17 | 'setuptools', 18 | ], 19 | extras_require={ 20 | 'test': [ 21 | 'plone.app.testing', 22 | ], 23 | }, 24 | entry_points=""" 25 | [z3c.autoinclude.plugin] 26 | target = plone 27 | """ 28 | ) 29 | -------------------------------------------------------------------------------- /test/tests/plone-develop/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | dir="$(dirname "$(readlink -f "$BASH_SOURCE")")" 4 | 5 | docker run -i --rm \ 6 | -e DEVELOP="/app/src/helloworld" \ 7 | -e PIP_PARAMS="-q --disable-pip-version-check" \ 8 | -v "$dir/helloworld:/app/src/helloworld" \ 9 | "$1" bin/python -c "from helloworld import say_hi; say_hi()" 10 | -------------------------------------------------------------------------------- /test/tests/plone-listenport/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | dir="$(dirname "$(readlink -f "$BASH_SOURCE")")" 5 | 6 | image="$1" 7 | 8 | PLONE_TEST_SLEEP=3 9 | PLONE_TEST_TRIES=5 10 | 11 | cname="plone-container-$RANDOM-$RANDOM" 12 | cid="$(docker run -d --name "$cname" -e LISTEN_PORT=8081 "$image")" 13 | trap "docker rm -vf $cid > /dev/null" EXIT 14 | 15 | get() { 16 | docker run --rm -i \ 17 | --link "$cname":plone \ 18 | --entrypoint /app/bin/python \ 19 | "$image" \ 20 | -c "from urllib.request import urlopen; con = urlopen('$1'); print(con.read())" 21 | } 22 | 23 | . "$dir/../../retry.sh" --tries "$PLONE_TEST_TRIES" --sleep "$PLONE_TEST_SLEEP" get "http://plone:8081" 24 | 25 | # Plone is up and running 26 | [[ "$(get 'http://plone:8081')" == *"Welcome to Plone!"* ]] 27 | -------------------------------------------------------------------------------- /test/tests/plone-relstorage/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | dir="$(dirname "$(readlink -f "$BASH_SOURCE")")" 5 | 6 | image="$1" 7 | 8 | PLONE_TEST_SLEEP=10 9 | PLONE_TEST_TRIES=10 10 | 11 | # Start Postgres 12 | zname="relstorage-container-$RANDOM-$RANDOM" 13 | zpull="$(docker pull postgres:9-alpine)" 14 | zid="$(docker run -d --name "$zname" -e POSTGRES_USER=plone -e POSTGRES_PASSWORD=plone -e POSTGRES_DB=plone postgres:14-alpine)" 15 | 16 | # Start Plone as RelStorage Client 17 | pname="plone-container-$RANDOM-$RANDOM" 18 | pid="$(docker run -d --name "$pname" --link=$zname:db -e RELSTORAGE_DSN="dbname='plone' user='plone' host='db' password='plone'" "$image")" 19 | 20 | # Tear down 21 | trap "docker rm -vf $pid $zid > /dev/null" EXIT 22 | 23 | get() { 24 | docker run --rm -i \ 25 | --link "$pname":plone \ 26 | --entrypoint /app/bin/python \ 27 | "$image" \ 28 | -c "from urllib.request import urlopen; con = urlopen('$1'); print(con.read())" 29 | } 30 | 31 | . "$dir/../../retry.sh" --tries "$PLONE_TEST_TRIES" --sleep "$PLONE_TEST_SLEEP" get "http://plone:8080" 32 | 33 | # Plone is up and running 34 | [[ "$(get 'http://plone:8080')" == *"Welcome to Plone!"* ]] 35 | -------------------------------------------------------------------------------- /test/tests/plone-shared-blob-dir/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | dir="$(dirname "$(readlink -f "$BASH_SOURCE")")" 5 | 6 | image="$1" 7 | 8 | PLONE_TEST_SLEEP=10 9 | PLONE_TEST_TRIES=10 10 | 11 | # Start ZEO server 12 | zname="zeo-container-$RANDOM-$RANDOM" 13 | zpull="$(docker pull plone/plone-zeo)" 14 | zid="$(docker run -d -v $zname:/data --name "$zname" plone/plone-zeo)" 15 | 16 | # Start Plone as ZEO Client 17 | pname="plone-container-$RANDOM-$RANDOM" 18 | pid="$(docker run -d -v $zname:/data -e ZEO_SHARED_BLOB_DIR=on --name "${pname}" --link=$zname:zeo -e ZEO_ADDRESS=zeo:8100 "$image")" 19 | 20 | # Tear down 21 | trap "docker rm -vf $pid $zid > /dev/null" EXIT 22 | 23 | get() { 24 | docker run --rm -i \ 25 | --link "${pname}":plone \ 26 | --entrypoint /app/bin/python \ 27 | "$image" \ 28 | -c "from urllib.request import urlopen; con = urlopen('$1'); print(con.read())" 29 | } 30 | 31 | . "$dir/../../retry.sh" --tries "$PLONE_TEST_TRIES" --sleep "$PLONE_TEST_SLEEP" get "http://plone:8080" 32 | 33 | # Plone is up and running 34 | [[ "$(get 'http://plone:8080')" == *"Welcome to Plone!"* ]] 35 | -------------------------------------------------------------------------------- /test/tests/plone-site/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | dir="$(dirname "$(readlink -f "$BASH_SOURCE")")" 5 | 6 | image="$1" 7 | 8 | PLONE_TEST_SLEEP=10 9 | PLONE_TEST_TRIES=10 10 | 11 | cname="plone-container-$RANDOM-$RANDOM" 12 | site="Plone$RANDOM" 13 | cid="$(docker run -d -e SITE=$site --name "$cname" "$image")" 14 | trap "docker rm -vf $cid > /dev/null" EXIT 15 | 16 | get() { 17 | docker run --rm -i \ 18 | --link "$cname":plone \ 19 | --entrypoint /app/bin/python \ 20 | "$image" \ 21 | -c "from urllib.request import urlopen; con = urlopen('$1'); print(con.read())" 22 | } 23 | 24 | get_auth() { 25 | docker run --rm -i \ 26 | --link "$cname":plone \ 27 | --entrypoint /app/bin/python \ 28 | "$image" \ 29 | -c "from urllib.request import urlopen, Request; request = Request('$1'); request.add_header('Authorization', 'Basic $2'); print(urlopen(request).read())" 30 | } 31 | 32 | 33 | . "$dir/../../retry.sh" --tries "$PLONE_TEST_TRIES" --sleep "$PLONE_TEST_SLEEP" get "http://plone:8080" 34 | 35 | # Plone is up and running 36 | [[ "$(get "http://plone:8080/$site")" == *"Welcome to Plone"* ]] 37 | -------------------------------------------------------------------------------- /test/tests/plone-zeoclient/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | dir="$(dirname "$(readlink -f "$BASH_SOURCE")")" 5 | 6 | image="$1" 7 | 8 | PLONE_TEST_SLEEP=3 9 | PLONE_TEST_TRIES=5 10 | 11 | # Start ZEO server 12 | zname="zeo-container-$RANDOM-$RANDOM" 13 | zpull="$(docker pull plone/plone-zeo)" 14 | zid="$(docker run -d --name "$zname" plone/plone-zeo)" 15 | 16 | # Start Plone as ZEO Client 17 | pname="plone-container-$RANDOM-$RANDOM" 18 | pid="$(docker run -d --name "$pname" --link=$zname:zeo -e ZEO_ADDRESS=zeo:8100 "$image")" 19 | 20 | # Tear down 21 | trap "docker rm -vf $pid $zid > /dev/null" EXIT 22 | 23 | get() { 24 | docker run --rm -i \ 25 | --link "$pname":plone \ 26 | --entrypoint /app/bin/python \ 27 | "$image" \ 28 | -c "from urllib.request import urlopen; con = urlopen('$1'); print(con.read())" 29 | } 30 | 31 | . "$dir/../../retry.sh" --tries "$PLONE_TEST_TRIES" --sleep "$PLONE_TEST_SLEEP" get "http://plone:8080" 32 | 33 | # Plone is up and running 34 | [[ "$(get 'http://plone:8080')" == *"Welcome to Plone!"* ]] 35 | -------------------------------------------------------------------------------- /test/tests/utc/expected-std-out.txt: -------------------------------------------------------------------------------- 1 | UTC 2 | -------------------------------------------------------------------------------- /test/tests/utc/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | docker run --rm --entrypoint date "$1" +%Z 5 | -------------------------------------------------------------------------------- /towncrier.toml: -------------------------------------------------------------------------------- 1 | [tool.towncrier] 2 | filename = "CHANGELOG.md" 3 | directory = "news/" 4 | title_format = "## {version} ({project_date})" 5 | underlines = ["", "", ""] 6 | template = "news/.towncrier_template.jinja" 7 | start_string = "\n" 8 | issue_format = "[#{issue}](https://github.com/plone/volto/issues/{issue})" 9 | 10 | [[tool.towncrier.type]] 11 | directory = "breaking" 12 | name = "Breaking" 13 | showcontent = true 14 | 15 | [[tool.towncrier.type]] 16 | directory = "feature" 17 | name = "Feature" 18 | showcontent = true 19 | 20 | [[tool.towncrier.type]] 21 | directory = "bugfix" 22 | name = "Bugfix" 23 | showcontent = true 24 | 25 | [[tool.towncrier.type]] 26 | directory = "internal" 27 | name = "Internal" 28 | showcontent = true 29 | 30 | [[tool.towncrier.type]] 31 | directory = "documentation" 32 | name = "Documentation" 33 | showcontent = true 34 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 6.1.1 2 | --------------------------------------------------------------------------------