diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..2f207c3f5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +/.github +/.husky +/packages/datagateway-*/cypress +/packages/datagateway-*/cypress.json +/packages/datagateway-*/server +/packages/datagateway-*/README.md +**/build +**/coverage +**/lib +**/node_modules +.gitignore +.prettierrc +codecov.yml +CODEOWNERS +LICENSE.md +README.md +Dockerfile diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index ae152da6e..927bc609e 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -2,7 +2,7 @@ name: CI Build on: workflow_dispatch: pull_request: - # By default, the pull_request event type is not triggered when a PR is merged into main + # By default, the pull_request event type is not triggered when a PR is merged into main or develop push: branches: - main @@ -457,3 +457,34 @@ jobs: with: name: Search-Screenshots path: packages/datagateway-search/cypress/screenshots + + docker: + # This job triggers only if all the other jobs succeed. It builds the Docker image and if successful, + # it pushes it to Harbor. + needs: [lint-and-unit-test, dataview-e2e-tests, download-e2e-tests, search-e2e-tests] + name: Docker + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3 + + - name: Login to Harbor + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0 + with: + registry: harbor.stfc.ac.uk/datagateway + username: ${{ secrets.HARBOR_USERNAME }} + password: ${{ secrets.HARBOR_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v4.4.0 + with: + images: harbor.stfc.ac.uk/datagateway/plugins + + - name: Build and push Docker image to Harbor + uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # v4.0.0 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..1694f438c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,75 @@ +# Dockerfile to build and serve datagateway + +# Build stage +FROM node:20.9-alpine3.18 as builder + +WORKDIR /datagateway-build + +# Enable dependency caching and share the cache between projects +ENV YARN_ENABLE_GLOBAL_CACHE=true +ENV YARN_GLOBAL_FOLDER=/root/.cache/.yarn + +COPY . . + +RUN --mount=type=cache,target=/root/.cache/.yarn/cache \ + set -eux; \ + # Set the React production variables which hold reference to the paths of the plugin builds \ + echo "REACT_APP_DATAVIEW_BUILD_DIRECTORY=/datagateway-dataview/" > packages/datagateway-dataview/.env.production; \ + echo "REACT_APP_DOWNLOAD_BUILD_DIRECTORY=/datagateway-download/" > packages/datagateway-download/.env.production; \ + echo "REACT_APP_SEARCH_BUILD_DIRECTORY=/datagateway-search/" > packages/datagateway-search/.env.production; \ + \ + cp packages/datagateway-dataview/public/datagateway-dataview-settings.example.json packages/datagateway-dataview/public/datagateway-dataview-settings.json; \ + cp packages/datagateway-download/public/datagateway-download-settings.example.json packages/datagateway-download/public/datagateway-download-settings.json; \ + cp packages/datagateway-search/public/datagateway-search-settings.example.json packages/datagateway-search/public/datagateway-search-settings.json; \ + \ + yarn workspaces focus --all --production; \ + yarn build; + +# Run stage +FROM httpd:2.4-alpine3.15 + +WORKDIR /usr/local/apache2/htdocs + +# Put the output of the build into an apache server +COPY --from=builder /datagateway-build/packages/datagateway-dataview/build/. ./datagateway-dataview/ +COPY --from=builder /datagateway-build/packages/datagateway-download/build/. ./datagateway-download/ +COPY --from=builder /datagateway-build/packages/datagateway-search/build/. ./datagateway-search/ + +RUN set -eux; \ + \ + # Enable mod_deflate \ + sed -i -e 's/^#LoadModule deflate_module/LoadModule deflate_module/' /usr/local/apache2/conf/httpd.conf; \ + # Compress all files except images \ + echo 'SetOutputFilter DEFLATE' >> /usr/local/apache2/conf/httpd.conf; \ + echo 'SetEnvIfNoCase Request_URI "\.(?:gif|jpe?g|png)$" no-gzip' >> /usr/local/apache2/conf/httpd.conf; \ + # Disable caching for .js, .json, and .html files \ + echo '' >> /usr/local/apache2/conf/httpd.conf; \ + echo ' Header set Cache-Control "no-cache"' >> /usr/local/apache2/conf/httpd.conf; \ + echo '' >> /usr/local/apache2/conf/httpd.conf; \ + \ + # Privileged ports are permitted to root only by default. \ + # setcap to bind to privileged ports (80) as non-root. \ + apk --no-cache add libcap; \ + setcap 'cap_net_bind_service=+ep' /usr/local/apache2/bin/httpd; \ + \ + # Change ownership of logs directory \ + chown www-data:www-data /usr/local/apache2/logs; \ + \ + # Change ownership of setting files \ + chown www-data:www-data /usr/local/apache2/htdocs/datagateway-dataview/datagateway-dataview-settings.json; \ + chown www-data:www-data /usr/local/apache2/htdocs/datagateway-download/datagateway-download-settings.json; \ + chown www-data:www-data /usr/local/apache2/htdocs/datagateway-search/datagateway-search-settings.json; + +# Switch to non-root user defined in httpd image +USER www-data + +ENV API_URL="/datagateway-api" +ENV DOWNLOAD_API_URL="http://localhost" +ENV ICAT_URL="http://localhost" +ENV IDS_URL="http://localhost" + +COPY docker/docker-entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["docker-entrypoint.sh"] + +CMD ["httpd-foreground"] +EXPOSE 80 diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh new file mode 100755 index 000000000..0b55121ef --- /dev/null +++ b/docker/docker-entrypoint.sh @@ -0,0 +1,34 @@ +#!/bin/sh -eu + +# Use a tempfile instead of sed -i so that only the file, not the directory needs to be writable +TEMPFILE="$(mktemp)" + +# Set values in datagateway-dataview-settings.json from environment variables +sed -e "s|\"idsUrl\": \".*\"|\"idsUrl\": \"$IDS_URL\"|" \ + -e "s|\"apiUrl\": \".*\"|\"apiUrl\": \"$API_URL\"|" \ + -e "s|\"downloadApiUrl\": \".*\"|\"downloadApiUrl\": \"$DOWNLOAD_API_URL\"|" \ + /usr/local/apache2/htdocs/datagateway-dataview/datagateway-dataview-settings.json > "$TEMPFILE" + +cat "$TEMPFILE" > /usr/local/apache2/htdocs/datagateway-dataview/datagateway-dataview-settings.json + +# Set values in datagateway-download-settings.json from environment variables +sed -e "s|\"idsUrl\": \".*\"|\"idsUrl\": \"$IDS_URL\"|" \ + -e "s|\"apiUrl\": \".*\"|\"apiUrl\": \"$API_URL\"|" \ + -e "s|\"downloadApiUrl\": \".*\"|\"downloadApiUrl\": \"$DOWNLOAD_API_URL\"|" \ + /usr/local/apache2/htdocs/datagateway-download/datagateway-download-settings.json > "$TEMPFILE" + +cat "$TEMPFILE" > /usr/local/apache2/htdocs/datagateway-download/datagateway-download-settings.json + +# Set values in datagateway-search-settings.json from environment variables +sed -e "s|\"idsUrl\": \".*\"|\"idsUrl\": \"$IDS_URL\"|" \ + -e "s|\"apiUrl\": \".*\"|\"apiUrl\": \"$API_URL\"|" \ + -e "s|\"downloadApiUrl\": \".*\"|\"downloadApiUrl\": \"$DOWNLOAD_API_URL\"|" \ + -e "s|\"icatUrl\": \".*\"|\"icatUrl\": \"$ICAT_URL\"|" \ + /usr/local/apache2/htdocs/datagateway-search/datagateway-search-settings.json > "$TEMPFILE" + +cat "$TEMPFILE" > /usr/local/apache2/htdocs/datagateway-search/datagateway-search-settings.json + +rm "$TEMPFILE" + +# Run the CMD instruction +exec "$@"