diff --git a/.github/workflows/build-master.yml b/.github/.old/build-master.yml similarity index 100% rename from .github/workflows/build-master.yml rename to .github/.old/build-master.yml diff --git a/.github/workflows/build-tags.yml b/.github/.old/build-tags.yml similarity index 100% rename from .github/workflows/build-tags.yml rename to .github/.old/build-tags.yml diff --git a/.github/workflows/docker-master.yml b/.github/.old/docker-master.yml similarity index 85% rename from .github/workflows/docker-master.yml rename to .github/.old/docker-master.yml index 6fa27c89..826cb673 100644 --- a/.github/workflows/docker-master.yml +++ b/.github/.old/docker-master.yml @@ -20,17 +20,17 @@ jobs: steps: - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Login to registry if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -43,7 +43,7 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} diff --git a/.github/workflows/docker-tags.yml b/.github/.old/docker-tags.yml similarity index 100% rename from .github/workflows/docker-tags.yml rename to .github/.old/docker-tags.yml diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 00000000..1e1719fe --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,80 @@ +name: Build and push ckan-docker image from PR Merge + +on: + pull_request: + types: + - closed + branches: + - master + - 'ckan-*.*.*' + - '!dev/ckan-*.*.*' + - '!feature/*' + - '!fix/*' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + TAG: ghcr.io/${{ github.repository }}:${{ github.head_ref }} + CONTEXT: . + BRANCH: ${{ github.head_ref }} + DOCKERFILE_PATH: /ckan + DOCKERFILE: Dockerfile + +jobs: + docker: + name: runner/build-docker-push:${{ github.head_ref }} + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Check out code + uses: actions/checkout@v4 + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/blob/${{ env.BRANCH }}/README.md + org.opencontainers.image.version=${{ env.BRANCH }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + push: true + tags: ${{ env.TAG }} + labels: ${{ steps.meta.outputs.labels }} + context: ${{ env.CONTEXT }} + file: ${{ env.CONTEXT }}${{ env.DOCKERFILE_PATH }}/${{ env.DOCKERFILE }} + + - name: Linting Dockerfile with hadolint in GH Actions + uses: hadolint/hadolint-action@v3.1.0 + with: + dockerfile: ${{ env.CONTEXT }}${{ env.DOCKERFILE_PATH }}/${{ env.DOCKERFILE }} + + - name: Run Trivy container image vulnerability scanner + uses: aquasecurity/trivy-action@0.12.0 + with: + image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.BRANCH }} + format: sarif + output: trivy-results.sarif + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + if: always() + with: + sarif_file: trivy-results.sarif \ No newline at end of file diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml new file mode 100644 index 00000000..dac2339a --- /dev/null +++ b/.github/workflows/docker-pr.yml @@ -0,0 +1,89 @@ +name: Test ckan-docker images (PR) + +on: + pull_request: + branches: + - master + - 'ckan-*.*.*' + - '!dev/ckan-*.*.*' + - '!feature/*' + - '!fix/*' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ckan-docker-spatial + CONTEXT: . + BRANCH: ${{ github.head_ref }} + DOCKERFILE_PATH: /ckan + DOCKERFILE: Dockerfile + HADOLINT_VERSION: 2.12.0 + +jobs: + docker: + name: runner/test-docker-pr:${{ github.head_ref }} + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Checkout + uses: actions/checkout@v4 + + - name: NGINX build + uses: docker/build-push-action@v5 + with: + context: ./nginx + file: ./nginx/Dockerfile + push: false + tags: mjanez/ckan-docker-nginx:test-build-only + + - name: Apache HTTP Server build + uses: docker/build-push-action@v5 + with: + context: ./apache + file: ./apache/Dockerfile + push: false + tags: mjanez/ckan-docker-apache:test-build-only + + - name: PostgreSQL build + uses: docker/build-push-action@v5 + with: + context: ./postgresql + file: ./postgresql/Dockerfile + push: false + tags: mjanez/ckan-docker-postgresql:test-build-only + + - name: Solr build + uses: docker/build-push-action@v5 + with: + context: ./solr + file: ./solr/Dockerfile + push: false + tags: mjanez/ckan-docker-solr:test-build-only + + - name: ckan-pycsw build + uses: docker/build-push-action@v4 + with: + context: ./ckan-pycsw + file: ./ckan-pycsw/Dockerfile + push: false + tags: mjanez/ckan-docker-pycsw:test-build-only + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Linting Dockerfiles and annotate code inline in the github PR viewer + id: hadolint + uses: jbergstroem/hadolint-gh-action@v1.11.0 + with: + dockerfile: ${{ env.CONTEXT }}${{ env.DOCKERFILE_PATH }}/${{ env.DOCKERFILE }} ${{ env.CONTEXT }}${{ env.DOCKERFILE_PATH }}/${{ env.DOCKERFILE }}.ghcr + version: ${{ env.HADOLINT_VERSION }} + annotate: true + error_level: -1 \ No newline at end of file diff --git a/ckan/Dockerfile b/ckan/Dockerfile index 66e9b08b..66836b06 100644 --- a/ckan/Dockerfile +++ b/ckan/Dockerfile @@ -1,11 +1,15 @@ FROM ghcr.io/mjanez/ckan-base-spatial:ckan-2.9.9 +LABEL maintainer="mnl.janez@gmail.com" # Set up environment variables -ENV APP_DIR=/srv/app \ - TZ=UTC +ENV APP_DIR=/srv/app +ENV TZ=UTC + +# Set working directory +WORKDIR ${APP_DIR} # requirements.txt files fixed until next releases -COPY req_fixes ${APP_DIR}/req_fixes +COPY req_fixes req_fixes # Extensions ### XLoader - 1.0.1 ### @@ -23,40 +27,40 @@ RUN echo ${TZ} > /etc/timezone && \ if ! [ /usr/share/zoneinfo/${TZ} -ef /etc/localtime ]; then cp /usr/share/zoneinfo/${TZ} /etc/localtime ; fi && \ # Install CKAN extensions echo "ckan/ckanext-xloader" && \ - pip3 install -e git+https://github.com/ckan/ckanext-xloader.git@1.0.1#egg=ckanext-xloader && \ - pip3 install -r ${APP_DIR}/src/ckanext-xloader/requirements.txt && \ - pip3 install -U requests[security] && \ + pip3 install --no-cache-dir -e git+https://github.com/ckan/ckanext-xloader.git@1.0.1#egg=ckanext-xloader && \ + pip3 install --no-cache-dir -r ${APP_DIR}/src/ckanext-xloader/requirements.txt && \ + pip3 install --no-cache-dir -U requests[security] && \ echo "ckan/ckanext-harvest" && \ - pip3 install -e git+https://github.com/ckan/ckanext-harvest.git@v1.5.1#egg=ckanext-harvest && \ - pip3 install -r ${APP_DIR}/src/ckanext-harvest/pip-requirements.txt && \ + pip3 install --no-cache-dir -e git+https://github.com/ckan/ckanext-harvest.git@v1.5.1#egg=ckanext-harvest && \ + pip3 install --no-cache-dir -r ${APP_DIR}/src/ckanext-harvest/pip-requirements.txt && \ echo "ckan/ckanext-geoview" && \ - pip3 install -e git+https://github.com/ckan/ckanext-geoview.git@v0.0.20#egg=ckanext-geoview && \ + pip3 install --no-cache-dir -e git+https://github.com/ckan/ckanext-geoview.git@v0.0.20#egg=ckanext-geoview && \ echo "ckan/ckanext-spatial" && \ - pip3 install -e git+https://github.com/ckan/ckanext-spatial.git@v2.0.0#egg=ckanext-spatial && \ - pip3 install -r ${APP_DIR}/req_fixes/ckanext-spatial_requirements.txt && \ + pip3 install --no-cache-dir -e git+https://github.com/ckan/ckanext-spatial.git@v2.0.0#egg=ckanext-spatial && \ + pip3 install --no-cache-dir -r ${APP_DIR}/req_fixes/ckanext-spatial_requirements.txt && \ echo "mjanez/ckanext-dcat (GeoDCAT-AP extended version)" && \ - pip3 install -e git+https://github.com/mjanez/ckanext-dcat.git@v1.2.0-geodcatap#egg=ckanext-dcat && \ - pip3 install -r ${APP_DIR}/src/ckanext-dcat/requirements.txt && \ + pip3 install --no-cache-dir -e git+https://github.com/mjanez/ckanext-dcat.git@v1.2.0-geodcatap#egg=ckanext-dcat && \ + pip3 install --no-cache-dir -r ${APP_DIR}/src/ckanext-dcat/requirements.txt && \ echo "ckan/ckanext-scheming" && \ - pip3 install -e git+https://github.com/ckan/ckanext-scheming.git@release-3.0.0#egg=ckanext-scheming && \ + pip3 install --no-cache-dir -e git+https://github.com/ckan/ckanext-scheming.git@release-3.0.0#egg=ckanext-scheming && \ echo "mjanez/ckanext-resourcedictionary" && \ - pip3 install -e git+https://github.com/mjanez/ckanext-resourcedictionary.git@v1.0.1#egg=ckanext-resourcedictionary && \ + pip3 install --no-cache-dir -e git+https://github.com/mjanez/ckanext-resourcedictionary.git@v1.0.1#egg=ckanext-resourcedictionary && \ echo "ckan/ckanext-pages" && \ - pip3 install -e git+https://github.com/ckan/ckanext-pages.git@v0.5.2#egg=ckanext-pages && \ + pip3 install --no-cache-dir -e git+https://github.com/ckan/ckanext-pages.git@v0.5.2#egg=ckanext-pages && \ echo "ckan/ckanext-pdfview" && \ - pip3 install -e git+https://github.com/ckan/ckanext-pdfview.git@0.0.8#egg=ckanext-pdfview && \ + pip3 install --no-cache-dir -e git+https://github.com/ckan/ckanext-pdfview.git@0.0.8#egg=ckanext-pdfview && \ echo "mjanez/ckanext-scheming_dcat" && \ - pip3 install -e git+https://github.com/mjanez/ckanext-scheming_dcat.git@v2.0.0#egg=ckanext_scheming_dcat && \ - pip3 install -r https://raw.githubusercontent.com/mjanez/ckanext-scheming_dcat/v2.0.0/requirements.txt + pip3 install --no-cache-dir -e git+https://github.com/mjanez/ckanext-scheming_dcat.git@v2.0.0#egg=ckanext_scheming_dcat && \ + pip3 install --no-cache-dir -r https://raw.githubusercontent.com/mjanez/ckanext-scheming_dcat/v2.0.0/requirements.txt # Used to configure the container environment by setting environment variables, creating users, running initialization scripts, .etc COPY docker-entrypoint.d/* /docker-entrypoint.d/ # Update who.ini with PROXY_CKAN_LOCATION -COPY setup/who.ini ${APP_DIR}/ +COPY setup/who.ini ./ # Apply any patches needed to CKAN core -COPY patches ${APP_DIR}/patches +COPY patches patches RUN for d in $APP_DIR/patches/*; do \ if [ -d $d ]; then \ @@ -66,4 +70,4 @@ RUN for d in $APP_DIR/patches/*; do \ fi ; \ done -CMD $APP_DIR/start_ckan.sh +CMD ["/bin/sh", "-c", "$APP_DIR/start_ckan.sh"] \ No newline at end of file diff --git a/ckan/Dockerfile.dev b/ckan/Dockerfile.dev index fb723658..c87c3ad0 100644 --- a/ckan/Dockerfile.dev +++ b/ckan/Dockerfile.dev @@ -1,9 +1,13 @@ FROM ghcr.io/mjanez/ckan-base-spatial:ckan-2.9.9-dev +LABEL maintainer="mnl.janez@gmail.com" # Set up environment variables -ENV APP_DIR=/srv/app \ - TZ=UTC \ - SRC_EXTENSIONS_DIR=/srv/app/src_extensions +ENV APP_DIR=/srv/app +ENV TZ=UTC +ENV SRC_EXTENSIONS_DIR=/srv/app/src_extensions + +# Set working directory +WORKDIR ${APP_DIR} RUN echo ${TZ} > /etc/timezone && \ set -ex && apk --no-cache add sudo && \ @@ -47,23 +51,29 @@ RUN echo ${TZ} > /etc/timezone && \ # to get them mounted in this image at runtime # Used to configure the container environment by setting environment variables, creating users, running initialization scripts, .etc -COPY docker-entrypoint.d/* /docker-entrypoint.d/ +COPY docker-entrypoint.d/* docker-entrypoint.d/ # Update who.ini with PROXY_CKAN_LOCATION -COPY setup/who.ini ${APP_DIR}/ +COPY setup/who.ini ./ # Override start_ckan.sh with DEV sh -COPY setup/start_ckan_development.sh.override ${APP_DIR}/start_ckan_development.sh -RUN chmod +x ${APP_DIR}/start_ckan_development.sh +COPY setup/start_ckan_development.sh.override start_ckan_development.sh +RUN chmod +x start_ckan_development.sh # Apply any patches needed to CKAN core or any of the built extensions (not the -# runtime mounted ones) -COPY patches ${APP_DIR}/patches +# runtime mounted ones!) +COPY patches patches RUN for d in $APP_DIR/patches/*; do \ if [ -d $d ]; then \ - for f in `ls $d/*.patch | sort -g`; do \ - cd $SRC_DIR/`basename "$d"` && echo "$0: Applying patch $f to $SRC_DIR/`basename $d`"; patch -p1 < "$f" ; \ - done ; \ + for f in `ls $d/*.patch | sort -g`; do \ + if [ -d $SRC_DIR/`basename "$d"` ]; then \ + cd $SRC_DIR/`basename "$d"` && \ + echo "$0: Applying patch $f to $SRC_DIR/`basename $d`" && \ + patch -p1 < "$f" ; \ + else \ + echo "$0: Skipping patch $f because directory $SRC_DIR/`basename $d` does not exist. Built the extension: `basename $d`" ; \ + fi \ + done ; \ fi ; \ - done \ No newline at end of file +done \ No newline at end of file diff --git a/ckan/Dockerfile.ghcr b/ckan/Dockerfile.ghcr index f0cad95c..899a2f11 100644 --- a/ckan/Dockerfile.ghcr +++ b/ckan/Dockerfile.ghcr @@ -1,23 +1,27 @@ FROM ghcr.io/mjanez/ckan-spatial:ckan-2.9.9 +LABEL maintainer="mnl.janez@gmail.com" # Set up environment variables ENV APP_DIR=/srv/app ENV TZ=UTC -RUN echo ${TZ} > /etc/timezone + +# Set working directory +WORKDIR ${APP_DIR} # Make sure both files are not exactly the same -RUN if ! [ /usr/share/zoneinfo/${TZ} -ef /etc/localtime ]; then \ - cp /usr/share/zoneinfo/${TZ} /etc/localtime ;\ +RUN echo ${TZ} > /etc/timezone && \ + if ! [ /usr/share/zoneinfo/${TZ} -ef /etc/localtime ]; then \ + cp /usr/share/zoneinfo/${TZ} /etc/localtime ; \ fi ; # Used to configure the container environment by setting environment variables, creating users, running initialization scripts, .etc -COPY docker-entrypoint.d/* /docker-entrypoint.d/ +COPY docker-entrypoint.d/* docker-entrypoint.d/ # Update who.ini with PROXY_CKAN_LOCATION -COPY setup/who.ini ${APP_DIR}/ +COPY setup/who.ini ./ # Apply any patches needed to CKAN core -COPY patches ${APP_DIR}/patches +COPY patches patches # Updated version of the Dockerfile RUN command that skips applying a patch if a reversed or previously applied patch is detected RUN for d in $APP_DIR/patches/*; do \ @@ -34,4 +38,4 @@ RUN for d in $APP_DIR/patches/*; do \ fi ; \ done -CMD $APP_DIR/start_ckan.sh +CMD ["/bin/sh", "-c", "$APP_DIR/start_ckan.sh"] \ No newline at end of file diff --git a/ckan/patches/ckanext-pages/00_root_path.patch b/ckan/patches/ckanext-pages/00_root_path.patch new file mode 100644 index 00000000..9e388115 --- /dev/null +++ b/ckan/patches/ckanext-pages/00_root_path.patch @@ -0,0 +1,32 @@ +diff --git a/ckanext/pages/plugin.py b/ckanext/pages/plugin.py +index fbd1be0..2882f97 100644 +--- a/ckanext/pages/plugin.py ++++ b/ckanext/pages/plugin.py +@@ -8,6 +8,7 @@ from ckan.plugins import toolkit as tk + + import ckan.plugins as p + from ckan.lib.helpers import build_nav_main as core_build_nav_main ++from ckan.lib.helpers import lang + + from ckanext.pages import actions, db + from ckanext.pages import auth +@@ -25,6 +26,10 @@ def build_pages_nav_main(*args): + about_menu = tk.asbool(tk.config.get('ckanext.pages.about_menu', True)) + group_menu = tk.asbool(tk.config.get('ckanext.pages.group_menu', True)) + org_menu = tk.asbool(tk.config.get('ckanext.pages.organization_menu', True)) ++ ++ # get the root path from the config and use lang if needed ++ root_path = tk.config.get('ckan.root_path', '') ++ root_path = root_path.replace('/{{LANG}}', '') if lang() == tk.config.get('ckan.locale_default') else root_path.replace('/{{LANG}}', '/{}'.format(lang())) + + new_args = [] + for arg in args: +@@ -51,7 +56,7 @@ def build_pages_nav_main(*args): + type_ = 'blog' if page['page_type'] == 'blog' else 'pages' + name = quote(page['name']) + title = html_escape(page['title']) +- link = tk.h.literal(u'{}'.format(type_, name, title)) ++ link = tk.h.literal(u'{}'.format(root_path, type_, name, title)) + if page['name'] == page_name: + li = tk.literal('
  • ') + link + tk.literal('
  • ') + else: