From fa1a60b3b275663450dcb0df58397c3194ea605e Mon Sep 17 00:00:00 2001
From: Konstantin Koval <kkb@ukr.net>
Date: Thu, 29 Aug 2024 16:25:25 +0300
Subject: [PATCH] Initial implementation

---
 .dockleignore                         |   5 ++
 .github/dependabot.yml                |  15 ++++
 .github/workflows/build.yml           | 101 ++++++++++++++++++++++++--
 .github/workflows/codeql-analysis.yml |  91 ++++++++++++++++++-----
 .github/workflows/tests.yml           |  30 +++++++-
 .hadolint.yaml                        |   4 +
 6 files changed, 220 insertions(+), 26 deletions(-)
 create mode 100644 .dockleignore
 create mode 100644 .github/dependabot.yml
 create mode 100644 .hadolint.yaml

diff --git a/.dockleignore b/.dockleignore
new file mode 100644
index 0000000..1993d8b
--- /dev/null
+++ b/.dockleignore
@@ -0,0 +1,5 @@
+# Allow the `latest` tag
+DKL-DI-0006
+
+# Don't care about signatures
+CIS-DI-0005
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..06b48a2
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,15 @@
+version: 2
+updates:
+  # Maintain dependencies for GitHub Actions
+  - package-ecosystem: "github-actions"
+    directory: "/.github/workflows"
+    schedule:
+      interval: "weekly"
+
+  # Maintain dependencies for Dockerfile
+  - package-ecosystem: "docker"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    ignore:
+      - dependency-name: "node"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d7601a4..6b44570 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -7,6 +7,8 @@ on:
     branches: [master]
     tags:
       - v*
+  schedule:
+    - cron: '18 1 * * 4'
 
 env:
   DOCKER_USERNAME: viktorstrate
@@ -15,16 +17,61 @@ env:
   PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
 
 jobs:
+  prepare:
+    name: Prepare the Matrix
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Prepare the Matrix
+        id: prepare_matrix
+        shell: bash
+        run: |
+          case ${{ github.event_name }} in
+            pull_request)
+              echo 'tags=[{"tag": "", "ref": "${{ github.ref }}"}]' >> $GITHUB_OUTPUT
+              ;;
+            push)
+              echo 'tags=[{"tag": "${{ github.ref_name }}", "ref": "${{ github.ref }}"}]' >> $GITHUB_OUTPUT
+              ;;
+            schedule)
+              git fetch --all
+              TAG=$(git describe --tags --abbrev=0 || exit 0)
+              if [ -z "$TAG" ]; then
+                echo 'tags=[{"tag": "${{ github.ref_name }}", "ref": "${{ github.ref }}"}]' >> $GITHUB_OUTPUT
+              else
+                echo 'tags=[{"tag": "${{ github.ref_name }}", "ref": "${{ github.ref }}"}, {"tag": "$TAG", "ref": "$(git show-ref --tags -d | grep "/$TAG$" | cut -d ' ' -f 2)"}]' >> $GITHUB_OUTPUT
+              fi
+              ;;
+            *)
+              echo "Run for '${{ github.event_name }}' is not expected"
+              echo 'tags=[{"tag": "", "ref": "${{ github.ref }}"}]' >> $GITHUB_OUTPUT
+              ;;
+          esac
+
+    outputs:
+      tags: ${{ steps.prepare_matrix.outputs.tags }}
+
   build:
     name: Build Docker Image
     runs-on: ubuntu-latest
-
+    needs: prepare
+    strategy:
+      fail-fast: false
+      matrix:
+        tags: ${{ fromJson(needs.prepare.outputs.tags) }}
     steps:
       - name: Delete huge unnecessary tools folder
         run: rm -rf /opt/hostedtoolcache
 
-      - name: Checkout
+      - name: Checkout ${{ matrix.tags.ref }}
         uses: actions/checkout@v4
+        with:
+          ref: ${{ matrix.tags.ref }}
+
+      - name: Fetch branches
+        run: git fetch --all
 
       - name: Set up QEMU
         uses: docker/setup-qemu-action@v3
@@ -36,7 +83,7 @@ jobs:
         uses: docker/setup-buildx-action@v3
 
       - name: Docker Login
-        if: success() && github.event_name != 'pull_request' && github.repository == 'photoview/photoview'
+        if: github.event_name != 'pull_request' && github.repository == 'photoview/photoview'
         uses: docker/login-action@v3
         with:
           username: ${{ env.DOCKER_USERNAME }}
@@ -62,16 +109,58 @@ jobs:
         uses: docker/build-push-action@v6
         with:
           context: .
+          sbom: true
+          provenance: mode=max
           platforms: ${{ env.PLATFORMS }}
           pull: true
           push: ${{ github.event_name != 'pull_request' && github.repository == 'photoview/photoview' }}
           tags: ${{ steps.docker_meta.outputs.tags }}
           labels: ${{ steps.docker_meta.outputs.labels }}
+          annotations: ${{ steps.docker_meta.outputs.annotations }}
           cache-from: type=gha
           cache-to: type=gha,mode=max
-          sbom: true
-          provenance: mode=max
-          annotations: ${{ steps.docker_meta.outputs.annotations }}
           build-args: |
             VERSION=${{ github.ref_name }}
             COMMIT_SHA=${{ github.sha }}
+
+  dockle:
+    name: Dockle Container Analysis
+    runs-on: ubuntu-latest
+    needs:
+      - prepare
+      - build
+    strategy:
+      fail-fast: false
+      matrix:
+        tags: ${{ fromJson(needs.prepare.outputs.tags) }}
+    if: ${{ github.event_name != 'pull_request' && github.repository == 'photoview/photoview' }}
+    steps:
+      # Makes sure your .dockleignore file is available to the next step
+      - name: Checkout ${{ matrix.tags.ref }}
+        uses: actions/checkout@v4
+        with:
+          ref: ${{ matrix.tags.ref }}
+
+      - name: Docker Login
+        uses: docker/login-action@v3
+        with:
+          username: ${{ env.DOCKER_USERNAME }}
+          password: ${{ env.DOCKER_PASSWORD }}
+
+      - name: Run Dockle for '${{ env.DOCKER_IMAGE }}:${{ matrix.tags.tag }}'
+        id: dockle
+        if: ${{ matrix.tags.tag != '' }}
+        uses: erzz/dockle-action@v1
+        with:
+          image: '${{ env.DOCKER_IMAGE }}:${{ matrix.tags.tag }}'
+          report-name: dockle-results-${{ matrix.tags.tag }}
+          report-format: sarif
+          failure-threshold: fatal
+          exit-code: 1
+          timeout: 5m
+
+      - name: Upload SARIF file
+        if: ${{ steps.dockle.conclusion == 'success' || steps.dockle.conclusion == 'failure' }}
+        uses: github/codeql-action/upload-sarif@v3
+        with:
+          sarif_file: dockle-results-${{ matrix.tags.tag }}.sarif
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 7140449..8a5ff06 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -7,34 +7,91 @@ on:
     # The branches below must be a subset of the branches above
     branches: [master]
   schedule:
-    - cron: '0 1 * * 4'
+    - cron: '37 1 * * 4'
 
 jobs:
+  create-matrix:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Get languages from repo
+        id: set-matrix
+        uses: advanced-security/set-codeql-language-matrix@v1
+        with:
+          access-token: ${{ github.token }}
+          endpoint: ${{ github.event.repository.languages_url }}
+    outputs:
+      matrix: ${{ steps.set-matrix.outputs.languages }}
+
   analyze:
     name: Analyze
-    if: github.repository == 'photoview/photoview'
-    runs-on: ubuntu-20.04
-
-    # strategy:
-    #   fail-fast: false
-    #   matrix:
-        # Override automatic language detection by changing the below list
-        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
-        # language: ['go', 'javascript']
-        # Learn more...
-        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
-
+    needs: create-matrix
+    if: ${{ needs.create-matrix.outputs.matrix != '[]' && github.repository == 'photoview/photoview' }}
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        language: ${{ fromJSON(needs.create-matrix.outputs.matrix) }}
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v2
+        uses: actions/checkout@v4
 
       # Initializes the CodeQL tools for scanning.
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@v1
+        uses: github/codeql-action/init@v3
         with:
-          languages: go, javascript
+          languages: ${{ matrix.language }}
           # Run further tests
           queries: security-extended, security-and-quality
 
+      # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+      - name: Autobuild
+        uses: github/codeql-action/autobuild@v3
+
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v1
+        uses: github/codeql-action/analyze@v3
+        with:
+          category: "/language:${{ matrix.language }}"
+
+  anchore:
+    name: Anchore scan code dependencies
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Generate report
+        id: scan
+        uses: anchore/scan-action@v4
+        with:
+          path: "."
+          fail-build: false
+          add-cpes-if-none: true
+
+      - name: Upload report
+        uses: github/codeql-action/upload-sarif@v3
+        if: ${{ steps.scan.conclusion == 'success' || steps.scan.conclusion == 'failure' }}
+        with:
+          sarif_file: ${{ steps.scan.outputs.sarif }}
+
+  hadolint:
+    name: Hadolint Dockerfile
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Lint Dockerfile
+        uses: hadolint/hadolint-action@v3.1.0
+        id: hadolint-report
+        with:
+          dockerfile: Dockerfile
+          config: ${{ github.workspace }}/.hadolint.yaml
+          output-file: hadolint.sarif
+          format: sarif
+          failure-threshold: ignore
+
+      - name: Upload report
+        uses: github/codeql-action/upload-sarif@v3
+        if: ${{ steps.hadolint-report.conclusion == 'success' || steps.hadolint-report.conclusion == 'failure' }}
+        with:
+          sarif_file: hadolint.sarif
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index ecf110e..fad8c35 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -17,14 +17,15 @@ jobs:
 
     services:
       mariadb:
-        image: mariadb:10.5
+        image: mariadb:lts
         env:
           MYSQL_DATABASE: photoview_test
           MYSQL_USER: photoview
           MYSQL_PASSWORD: photosecret
           MYSQL_RANDOM_ROOT_PASSWORD: yes
+        # https://github.com/MariaDB/mariadb-docker/issues/497
         options: >-
-          --health-cmd="mysqladmin ping"
+          --health-cmd="mariadb-admin ping"
           --health-interval=10s
           --health-timeout=5s
           --health-retries=5
@@ -32,7 +33,7 @@ jobs:
           - 3306:3306
 
       postgres:
-        image: postgres:13.2
+        image: postgres:16-alpine
         env:
           POSTGRES_USER: photoview
           POSTGRES_PASSWORD: photosecret
@@ -64,6 +65,7 @@ jobs:
         cache-to: type=gha,mode=max
 
     - name: Test
+      id: test
       run: |
         docker run --name test --network host \
             -e PHOTOVIEW_DATABASE_DRIVER=${{ matrix.database }} \
@@ -76,6 +78,7 @@ jobs:
 
     - name: Upload coverage
       uses: codecov/codecov-action@v4
+      if: ${{ steps.test.conclusion == 'success' || steps.test.conclusion == 'failure' }}
       with:
         flags: api-${{ matrix.database }}
 
@@ -102,11 +105,32 @@ jobs:
         cache-to: type=gha,mode=max
 
     - name: Test
+      id: test
       run: |
         docker run --name test photoview/ui npm run test:ci
         docker cp test:/app/ui/coverage ./ui/
 
     - name: Upload coverage
       uses: codecov/codecov-action@v4
+      if: ${{ steps.test.conclusion == 'success' || steps.test.conclusion == 'failure' }}
       with:
         flags: ui
+
+    - name: Run ESLint
+      working-directory: ui
+      run: |
+        npm run lint:ci || true
+        echo "--------------------------"
+        echo "ESLint execution results :"
+        echo "--------------------------"
+        cat ./eslint-report.txt || echo "ESLint report file not found."
+
+    - name: ESLint artifact
+      id: eslint-artifact
+      uses: actions/upload-artifact@v4
+      with:
+        name: ESLint-report
+        path: ./ui/eslint-report.txt
+        if-no-files-found: warn
+        compression-level: 9
+        overwrite: true
diff --git a/.hadolint.yaml b/.hadolint.yaml
new file mode 100644
index 0000000..40d1024
--- /dev/null
+++ b/.hadolint.yaml
@@ -0,0 +1,4 @@
+ignored:
+  - DL3008 # Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
+  - DL3015 # Avoid additional packages by specifying `--no-install-recommends`.
+  - SC1091 # Not following: File not included in mock.
