diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..466e9d4d1 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Team Hermes are the owners of all files in this repository +* @ScilifelabDataCentre/teamhermes diff --git a/.github/workflows/docker-compose-tests.yml b/.github/workflows/docker-compose-tests.yml index 4b3e32b4b..8dd1e6c79 100644 --- a/.github/workflows/docker-compose-tests.yml +++ b/.github/workflows/docker-compose-tests.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v3 - name: Run tests against database container - run: docker-compose -f docker-compose.yml -f tests/docker-compose-test.yml up --build --exit-code-from backend + run: docker compose -f docker-compose.yml -f tests/docker-compose-test.yml up --build --exit-code-from backend - name: Setup upterm session uses: lhotari/action-upterm@v1 diff --git a/.github/workflows/publish_and_trivyscan.yml b/.github/workflows/publish_and_trivyscan.yml index 4e3ea4664..7bd44a6c8 100644 --- a/.github/workflows/publish_and_trivyscan.yml +++ b/.github/workflows/publish_and_trivyscan.yml @@ -15,7 +15,6 @@ name: Publish to GHCR (+ Trivy scan) on: workflow_dispatch: - branches: [dev] pull_request: push: branches: @@ -24,7 +23,56 @@ on: release: types: [published] jobs: + build_tech_overview: + name: Build technical overview + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: Build tech overview PDF + uses: docker://pandoc/latex:3.2 + with: + entrypoint: /bin/sh + args: >- + -c " + tlmgr update --self && + tlmgr install cm-super fontaxes lato pdflscape xkeyval && + updmap-sys && + pandoc + --output=dds_web/static/dds-technical-overview.pdf + doc/technical-overview.md + " + - name: Upload technical overview PDF + uses: actions/upload-artifact@v4 + with: + name: technical-overview-pdf + path: dds_web/static/dds-technical-overview.pdf + build_troubleshooting: + name: Build troubleshooting guide + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: Build troubleshooting guide + uses: docker://pandoc/latex:3.2 + with: + entrypoint: /bin/sh + args: >- + -c " + tlmgr update --self && + tlmgr install cm-super fontaxes lato xkeyval && + updmap-sys && + pandoc + --output=dds_web/static/dds-troubleshooting.pdf + doc/troubleshooting.md + " + - name: Upload troubleshooting PDF + uses: actions/upload-artifact@v4 + with: + name: troubleshooting-pdf + path: dds_web/static/dds-troubleshooting.pdf push_to_registry: + needs: [build_tech_overview, build_troubleshooting] if: github.repository == 'ScilifelabDataCentre/dds_web' name: Push image runs-on: ubuntu-latest @@ -37,23 +85,33 @@ jobs: cancel-in-progress: true steps: - name: Check out the repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Log in to Github Container Repository - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Download technical overview PDF + uses: actions/download-artifact@v4 + with: + name: technical-overview-pdf + path: dds_web/static/dds-technical-overview.pdf + - name: Download troubleshooting PDF + uses: actions/download-artifact@v4 + with: + name: troubleshooting-pdf + path: dds_web/static/dds-troubleshooting.pdf - name: Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: | ghcr.io/scilifelabdatacentre/dds-backend - name: Ensure lowercase name run: echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV - name: Build for scan - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: file: Dockerfiles/backend.Dockerfile context: . @@ -67,7 +125,7 @@ jobs: output: "trivy-results.sarif" severity: "CRITICAL,HIGH" - name: Upload Trivy scan results to Github Security tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: "trivy-results.sarif" category: trivy-build diff --git a/.github/workflows/trivy-scan-branch.yml b/.github/workflows/trivy-scan-branch.yml index 777e9a633..e17032ce5 100644 --- a/.github/workflows/trivy-scan-branch.yml +++ b/.github/workflows/trivy-scan-branch.yml @@ -31,7 +31,7 @@ jobs: severity: "CRITICAL,HIGH" - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: "trivy-results.sarif" category: trivy diff --git a/.github/workflows/trivy-scheduled-dev.yml b/.github/workflows/trivy-scheduled-dev.yml index b055362c4..1399be061 100644 --- a/.github/workflows/trivy-scheduled-dev.yml +++ b/.github/workflows/trivy-scheduled-dev.yml @@ -6,10 +6,7 @@ # --------------------------------- name: Trivy - ghcr image scan - dev on: - workflow_dispatch: - branches: - - dev - schedule: + schedule: # Since dev is the default branch of the repo don't specify - cron: "0 9,12,15 * * *" jobs: scan: @@ -26,7 +23,7 @@ jobs: run: echo REPOSITORY_OWNER=$(echo ${{ github.repository_owner }} | tr "[:upper:]" "[:lower:]") >> $GITHUB_ENV - name: Run Trivy on latest dev image - uses: aquasecurity/trivy-action@0.7.1 + uses: aquasecurity/trivy-action@0.24.0 with: image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:dev" format: "sarif" @@ -34,7 +31,7 @@ jobs: severity: "CRITICAL,HIGH" - name: Upload Trivy scan results to dev branch GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: "trivy-results-dev.sarif" category: trivy-dev diff --git a/.github/workflows/trivy-scheduled-master.yml b/.github/workflows/trivy-scheduled-master.yml index 238f0b266..4ef9fa58b 100644 --- a/.github/workflows/trivy-scheduled-master.yml +++ b/.github/workflows/trivy-scheduled-master.yml @@ -6,9 +6,6 @@ # --------------------------------- name: Trivy - ghcr image scan - master on: - workflow_dispatch: - branches: - - master schedule: - cron: "0 7,15 * * *" jobs: @@ -28,7 +25,7 @@ jobs: run: echo REPOSITORY_OWNER=$(echo ${{ github.repository_owner }} | tr "[:upper:]" "[:lower:]") >> $GITHUB_ENV - name: Run Trivy on latest release image - uses: aquasecurity/trivy-action@0.7.1 + uses: aquasecurity/trivy-action@0.24.0 with: image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:latest" format: "sarif" @@ -36,7 +33,7 @@ jobs: severity: "CRITICAL,HIGH" - name: Upload Trivy scan results to master branch GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: "trivy-results-master.sarif" category: trivy-master diff --git a/.prettierignore b/.prettierignore index c8e83654e..c39e6b6bb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,6 @@ *.html dds_web/static/css dds_web/static/node_modules +# Would like to enable this but prettier isn't correctly formatting grid tables for pandoc +# We need grid table markdown due to having tables with multi-column/row spans +doc/technical-overview.md diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 76dfc6aba..1d635628a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,22 @@ Changelog ========== +.. _2.8.0: + +2.8.0 - 2024-09-24 +~~~~~~~~~~~~~~~~~~~~~~~ + +- New features: + - Technical Overview and Troubleshooting updated and made available as pdf. + - Added Flask command for updating units quota. +- Dependencies: + - `certifi` from `2023.07.22` to `2024.7.4` + - `requests` from `2.31.0` to `2.32.0` + - `wrapt` from `1.13.3` to `1.14.0` +- Dependencies (tests): + - `pyfakefs` from `4.5.5` to `5.3.0` +- Update base image for the docker containers from `python:3.11-alpine` to `python:3.12-alpine` + .. _2.7.1: 2.7.1 - 2024-06-26 diff --git a/Dockerfiles/backend.Dockerfile b/Dockerfiles/backend.Dockerfile index 5f013b78e..a98474321 100644 --- a/Dockerfiles/backend.Dockerfile +++ b/Dockerfiles/backend.Dockerfile @@ -3,7 +3,7 @@ ############################# # Set official image -- parent image -FROM python:3.11-alpine as base +FROM python:3.12-alpine as base ARG USERNAME=dds-user ARG USER_UID=1001 diff --git a/Dockerfiles/cli.Dockerfile b/Dockerfiles/cli.Dockerfile index 195b7ead6..b1d86654f 100644 --- a/Dockerfiles/cli.Dockerfile +++ b/Dockerfiles/cli.Dockerfile @@ -1,5 +1,5 @@ # Set official image -FROM python:3.11-alpine as base +FROM python:3.12-alpine as base # Update and upgrade RUN apk update && apk upgrade diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..f75e6ba30 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +# For Linux and MacOS users only and assumes brew is already installed +TECH_MARKDOWN_FILE := doc/technical-overview.md +TECH_OVERVIEW_PDF := dds_web/static/dds-technical-overview.pdf +TROUBLE_MARKDOWN_FILE := doc/troubleshooting.md +TROUBLE_PDF := dds_web/static/dds-troubleshooting.pdf + +install_pandoc: + brew install pandoc basictex librsvg + +update_tlmgr: + tlmgr update --self + tlmgr install cm-super fontaxes lato pdflscape xkeyval + +build_tech_overview_pdf: update_tlmgr $(TECH_MARKDOWN_FILE) + pandoc $(TECH_MARKDOWN_FILE) -o $(TECH_OVERVIEW_PDF) + +build_troubleshooting_pdf: update_tlmgr $(TROUBLE_MARKDOWN_FILE) + pandoc $(TROUBLE_MARKDOWN_FILE) -o $(TROUBLE_PDF) + +build_docs: build_tech_overview_pdf build_troubleshooting_pdf + +.PHONY: update_tlmgr build_tech_overview_pdf build_troubleshooting_pdf build_docs all diff --git a/README.md b/README.md index 9b2adad39..0bb2111a0 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ Documentation - + Technical Overview Architecture Decision Record - + Troubleshooting diff --git a/SPRINTLOG.md b/SPRINTLOG.md index d0efaacc9..c8c7c7d62 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -231,11 +231,11 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe # 2023-03-17 - 2023-03-31 -_Nothing merged in CLI during this sprint_ +_Nothing merged during this sprint_ # 2023-03-31 - 2023-04-14 -_Nothing merged in CLI during this sprint_ +_Nothing merged during this sprint_ # 2023-04-14 - 2023-04-28 @@ -404,3 +404,29 @@ _Nothing merged in CLI during this sprint_ - Update pymysql to address cve ([#1534](https://github.com/ScilifelabDataCentre/dds_web/pull/1534)) - Update authlib to address cve ([#1535](https://github.com/ScilifelabDataCentre/dds_web/pull/1535)) - Update node packages to address cve ([#1536](https://github.com/ScilifelabDataCentre/dds_web/pull/1536)) + +# 2024-07-15 - 2024-07-26 + +- Move raw Technical Overview doc to repo, add page numbers ([#1539](https://github.com/ScilifelabDataCentre/dds_web/pull/1539)) +- Small updates to Technical Overview contents ([#1540](https://github.com/ScilifelabDataCentre/dds_web/pull/1540)) +- Build Technical Overview PDF in GitHub Actions, rename to include DDS and remove option to view on GitHub ([#1541](https://github.com/ScilifelabDataCentre/dds_web/pull/1541/)) +- Fixed index out of range when listing files from root ([#1543](https://github.com/ScilifelabDataCentre/dds_web/pull/1543/)) +- Update Trivy GitHub Actions ([#1545](https://github.com/ScilifelabDataCentre/dds_web/pull/1545)) + +# 2024-07-29 - 2024-08-09 + +- Move raw troubleshooting doc to repo and make small updates ([#1546](https://github.com/ScilifelabDataCentre/dds_web/pull/1546)) + +# 2024-08-12 - 2024-08-23 + +_Nothing merged during this sprint_ + +# 2024-08-26 - 2024-09-06 + +- Update certifi to remove GLOBALISSUER certicates ([#1549](https://github.com/ScilifelabDataCentre/dds_web/pull/1549)) +- Add CODEOWNERS file in order to define Team Hermes as owners of all files in repository ([#708](https://github.com/ScilifelabDataCentre/dds_web/pull/708)) + +# 2024-09-09 - 2024-09-20 + +- Flask command to update unit quotas ([#1551](https://github.com/ScilifelabDataCentre/dds_web/pull/1551)) +- Bump python base image to 3.12 and related libraries in both web and client([#1548](https://github.com/ScilifelabDataCentre/dds_web/pull/1548)) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index cb63b3faa..5afd64728 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -282,13 +282,15 @@ def load_user(user_id): send_usage, collect_stats, monitor_usage, - update_unit, + update_unit_sto4, + update_unit_quota, ) # Add flask commands - general app.cli.add_command(fill_db_wrapper) app.cli.add_command(create_new_unit) - app.cli.add_command(update_unit) + app.cli.add_command(update_unit_sto4) + app.cli.add_command(update_unit_quota) app.cli.add_command(update_uploaded_file_with_log) app.cli.add_command(lost_files_s3_db) diff --git a/dds_web/api/files.py b/dds_web/api/files.py index 32953ed2b..27ec6a6e7 100644 --- a/dds_web/api/files.py +++ b/dds_web/api/files.py @@ -291,71 +291,30 @@ class ListFiles(flask_restful.Resource): def get(self): """Get a list of files within the specified folder.""" + # Verify project ID and access + project = project_schemas.ProjectRequiredSchema().load(flask.request.args) + + if auth.current_user().role == "Researcher" and project.current_status == "In Progress": + raise AccessDeniedError( + message="The project status must be 'Available'. Please wait for unit to release the data." + ) + if "api/v1" in flask.request.path: # requests comming from api/v1 should be handled as before - return self.old_get() + return self.old_get(project) elif "api/v3" in flask.request.path: - # Verify project ID and access - project = project_schemas.ProjectRequiredSchema().load(flask.request.args) - - if auth.current_user().role == "Researcher" and project.current_status == "In Progress": - raise AccessDeniedError( - message="The project status must be 'Available'. Please wait for unit to release the data." - ) # Check if to return file size show_size = flask.request.args.get("show_size", type=inputs.boolean, default=False) # Check if to get from root or folder - subpath = "." - if flask.request.args.get("subpath"): - subpath = flask.request.args.get("subpath").rstrip(os.sep) - - files_folders = list() - - # Check project not empty - if project.num_files == 0: - return {"num_items": 0, "message": f"The project {project.public_id} is empty."} + subpath = flask.request.args.get("subpath", default=".").rstrip(os.sep) + subpath = "." if subpath == "" else subpath + return self.get_files_folders(project, subpath, show_size) - # Get files and folders - distinct_files, distinct_folders = self.items_in_subpath( - project=project, folder=subpath - ) - - # Collect file and folder info to return to CLI - if distinct_files: - for x in distinct_files: - info = { - "name": x[0] if subpath == "." else x[0].split(os.sep)[-1], - "folder": False, - } - if show_size: - info.update({"size": float(x[1])}) - files_folders.append(info) - if distinct_folders: - for x in distinct_folders: - info = { - "name": x if subpath == "." else x.split(os.sep)[-1], - "folder": True, - } - - if show_size: - folder_size = self.get_folder_size(project=project, folder_name=x) - info.update({"size": float(folder_size)}) - files_folders.append(info) - - return {"files_folders": files_folders} - - def old_get(self): + def old_get(self, project): """Implementation of old get method. Should be removed when api/v1 is removed.""" - # Verify project ID and access - project = project_schemas.ProjectRequiredSchema().load(flask.request.args) - - if auth.current_user().role == "Researcher" and project.current_status == "In Progress": - raise AccessDeniedError( - message="The project status must be 'Available'. Please wait for unit to release the data." - ) extra_args = flask.request.get_json(silent=True) if extra_args is None: @@ -365,16 +324,17 @@ def old_get(self): show_size = extra_args.get("show_size") # Check if to get from root or folder - subpath = "." - if extra_args.get("subpath"): - subpath = extra_args.get("subpath").rstrip(os.sep) + subpath = extra_args.get("subpath", ".").rstrip(os.sep) + subpath = "." if subpath == "" else subpath - files_folders = list() + return self.get_files_folders(project, subpath, show_size) - # Check project not empty + def get_files_folders(self, project, subpath, show_size): if project.num_files == 0: return {"num_items": 0, "message": f"The project {project.public_id} is empty."} + files_folders = list() + # Get files and folders distinct_files, distinct_folders = self.items_in_subpath(project=project, folder=subpath) diff --git a/dds_web/commands.py b/dds_web/commands.py index eca546554..7cd3d8777 100644 --- a/dds_web/commands.py +++ b/dds_web/commands.py @@ -157,15 +157,15 @@ def create_new_unit( gc.collect() -@click.command("update-unit") +@click.command("update-unit-sto4") @click.option("--unit-id", "-u", type=str, required=True) @click.option("--sto4-endpoint", "-se", type=str, required=True) @click.option("--sto4-name", "-sn", type=str, required=True) @click.option("--sto4-access", "-sa", type=str, required=True) @click.option("--sto4-secret", "-ss", type=str, required=True) @flask.cli.with_appcontext -def update_unit(unit_id, sto4_endpoint, sto4_name, sto4_access, sto4_secret): - """Update unit info.""" +def update_unit_sto4(unit_id, sto4_endpoint, sto4_name, sto4_access, sto4_secret): + """Update unit sto4 storage info.""" # Imports import rich.prompt from dds_web import db @@ -205,6 +205,43 @@ def update_unit(unit_id, sto4_endpoint, sto4_name, sto4_access, sto4_secret): gc.collect() +@click.command("update-unit-quota") +@click.option("--unit-id", "-u", type=str, required=True) +@click.option("--quota", "-q", type=int, required=True) +@flask.cli.with_appcontext +def update_unit_quota(unit_id, quota): + """Update unit quota. The input is in GB.""" + # Imports + import rich.prompt + from dds_web import db + from dds_web.database import models + + # Get unit + unit: models.Unit = models.Unit.query.filter_by(public_id=unit_id).one_or_none() + if not unit: + flask.current_app.logger.error(f"There is no unit with the public ID '{unit_id}'.") + sys.exit(1) + + # ask the user for confirmation + do_update = rich.prompt.Confirm.ask( + f"Current quota for unit '{unit_id}' is {round(unit.quota / 1000 ** 3,2)} GB. \n" + f"You are about to update the quota to {quota} GB ({quota * 1000 ** 3} bytes). \n" + "Are you sure you want to continue?" + ) + if not do_update: + flask.current_app.logger.info( + f"Cancelling quota update for unit '{unit_id}'. The quota is still {round(unit.quota / 1000 ** 3,2)} GB. ({unit.quota} bytes.)" + ) + return + + # Set sto4 info + quota_bytes = quota * 1000**3 + unit.quota = quota_bytes + db.session.commit() + + flask.current_app.logger.info(f"Unit '{unit_id}' updated successfully") + + @click.command("update-uploaded-file") @click.option("--project", "-p", type=str, required=True) @click.option("--path-to-log-file", "-fp", type=str, required=True) diff --git a/dds_web/templates/technical_overview.html b/dds_web/templates/technical_overview.html index b9a149ae3..388752e1a 100644 --- a/dds_web/templates/technical_overview.html +++ b/dds_web/templates/technical_overview.html @@ -3,8 +3,7 @@ {% block body %}

Technical Overview of the Data Delivery System

-

The technical overview provides an overview of how the DDS works and is meant to be used. The document can be found on GitHub or downloaded as a PDF.

-

Read on GitHub Download the PDF 

-
+

The technical overview provides an overview of how the DDS works and is meant to be used.

+

Read the PDF 

{% endblock %} diff --git a/dds_web/templates/troubleshooting.html b/dds_web/templates/troubleshooting.html index 9f8d60351..248b0b8ff 100644 --- a/dds_web/templates/troubleshooting.html +++ b/dds_web/templates/troubleshooting.html @@ -5,8 +5,7 @@

Troubleshooting the DDS


Experiencing issues with the DDS?

Please read the troubleshooting document which provides answers to previously asked questions and possible solutions to issues that have been reported.

-

The document can be found on GitHub or downloaded as a PDF.

-

Read on GitHub Download the PDF 

+

Download the PDF 


Did the troubleshooting document not help?

Please go through the following steps:

@@ -16,8 +15,8 @@

Did the troubleshooting document not help?

  • Read through the sections in the technical overview relevant to your issue and check the topics below for possible solutions.

  • -

    Does the issue persist and / or the provided information not help?

    -

    If you're a data producer , using the DDS for data deliveries to your users: contact the +

    Does the issue persist and/or the provided information not help?

    +

    If you're a data producer, using the DDS for data deliveries to your users: contact the

    -

    If you're a data recipient , using the DDS to download your data from a data producing SciLifeLab unit: contact the SciLifeLab unit responsible for delivering the data to you.

    +

    If you're a data recipient, using the DDS to download your data from a data producing SciLifeLab unit: contact the SciLifeLab unit responsible for delivering the data to you.

    Please provide the following information: