diff --git a/.github/workflows/build-app-candidate.yml b/.github/workflows/build-app-candidate.yml index 0d1e778..b8a2e2c 100644 --- a/.github/workflows/build-app-candidate.yml +++ b/.github/workflows/build-app-candidate.yml @@ -34,63 +34,24 @@ jobs: with: images: ghcr.io/${{ github.repository }}/app tags: | - type=raw,value=candidade + type=raw,value=candidate - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: ./client - push: true - tags: candidate - cache-from: type=gha - cache-to: type=gha,mode=max - build-args: | - VITE_BUILD=v${DATE}.${{ github.run_number }}" -name: Build App Candidate - -on: - workflow_dispatch: - -jobs: - build-and-push-app: - name: Build & Push App - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }}/app - tags: | - type=raw,value=candidade + - name: Generate version tag + id: version + run: | + DATE=$(date +'%Y.%m.%d') + TAG="app-v${DATE}.${{ github.run_number }}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "Generated tag: ${TAG}" - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: ./client push: true - tags: candidate + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max build-args: | - VITE_BUILD=v${DATE}.${{ github.run_number }}" + VITE_BUILD=${{ steps.version.outputs.tag }} diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml index 27908be..2567b91 100644 --- a/.github/workflows/client-ci.yml +++ b/.github/workflows/client-ci.yml @@ -1,10 +1,11 @@ -name: Frontend CI +name: Frontend PR on: workflow_dispatch: - push: + pull_request: + types: [opened, synchronize, reopened] branches: - - '**' + - 'main' paths: - 'client/**/*.html' - 'client/**/*.png' @@ -18,8 +19,8 @@ on: - '.github/workflows/client-ci.yml' jobs: - build-and-push: - name: Frontend CI + run-checks: + name: Checks runs-on: ubuntu-latest permissions: contents: read @@ -51,3 +52,55 @@ jobs: - name: Run tests run: npm run test:no-watch working-directory: ./client + + build-and-push: + name: Build & Push + runs-on: ubuntu-latest + needs: ["run-checks"] + permissions: + contents: write + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }}/app + tags: | + type=raw,value=candidate + + - name: Generate version tag + id: version + run: | + DATE=$(date +'%Y.%m.%d') + TAG="app-v${DATE}.${{ github.run_number }}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "Generated tag: ${TAG}" + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: ./client + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + VITE_BUILD=${{ steps.version.outputs.tag }} diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml new file mode 100644 index 0000000..6347f44 --- /dev/null +++ b/.github/workflows/deploy-stg.yml @@ -0,0 +1,136 @@ +name: Deploy to staging + +on: + workflow_dispatch: + workflow_run: + workflows: [ "Backend PR", "Frontend PR" ] + types: [ completed ] + +jobs: + terraform-plan-stg: + name: Plan changs to staging + runs-on: ubuntu-latest + outputs: + no_changes: ${{ steps.check-changes.outputs.no_changes }} + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Setup kubectl + uses: azure/setup-kubectl@v4 + + - name: Setup Kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_DATA }}" | base64 -d > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Validate cluster access + run: | + kubectl cluster-info + kubectl get namespace tasknote-stg + + - name: Determine deployment values + id: deploy-vars + run: | + backend_image="ghcr.io/rmcampos/tasknote/api:candidate" + frontend_image="ghcr.io/rmcampos/tasknote/app:candidate" + + echo "backend_image=$backend_image" >> "$GITHUB_OUTPUT" + echo "frontend_image=$frontend_image" >> "$GITHUB_OUTPUT" + + - name: Terraform Fmt -check -diff + working-directory: terraform-stg + run: terraform fmt -check -diff + + - name: Terraform Init + working-directory: terraform-stg + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: terraform init -input=false + + - name: Terraform Validate + working-directory: terraform-stg + run: terraform validate + + - name: Terraform Plan + id: check-changes + working-directory: terraform-stg + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: | + timeout 1m terraform plan -input=false -out=tfplan \ + -var="db_user=${{ secrets.DB_USER }}" \ + -var="db_password=${{ secrets.DB_PASSWORD }}" \ + -var="db_name=${{ secrets.DB_NAME }}" \ + -var="security_key=${{ secrets.JWT_SECURITY_KEY }}" \ + -var="mailgun_apikey=${{ secrets.MAILGUN_API_KEY }}" \ + -var="backend_image=${{ steps.deploy-vars.outputs.backend_image }}" \ + -var="frontend_image=${{ steps.deploy-vars.outputs.frontend_image }}" + terraform show -json tfplan > tfplan.json + if jq -e '.resource_changes | length == 0' tfplan.json >/dev/null; then + echo "no_changes=true" >> "$GITHUB_OUTPUT" + echo "No changes to apply." + exit 0 + else + echo "Changes detected. Proceeding with apply" + echo "no_changes=false" >> "$GITHUB_OUTPUT" + fi + + - name: Upload plan artifact + uses: actions/upload-artifact@v4 + with: + name: tfplan + path: terraform/tfplan + + terraform-apply: + runs-on: ubuntu-latest + needs: terraform-plan-stg + if: > + (github.event_name == 'push' || github.event_name == 'workflow_run') + && needs.terraform-plan-stg.outputs.no_changes == 'false' + environment: + name: staging + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Download plan artifact + uses: actions/download-artifact@v4 + with: + name: tfplan + path: terraform + + - name: Setup Kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_DATA }}" | base64 -d > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Terraform Init + working-directory: terraform-stg + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: terraform init -input=false + + - name: Terraform Apply + working-directory: terraform-stg + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: timeout 1m terraform apply tfplan diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 918c0c0..f1e34a5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,4 +1,4 @@ -name: Deploy to K3s +name: Deploy to prod on: workflow_dispatch: @@ -14,7 +14,7 @@ on: required: false default: "true" workflow_run: - workflows: [ "Backend Build & Push", "Frontend Build & Push" ] + workflows: [ "Backend Main", "Frontend Main" ] types: [ completed ] jobs: diff --git a/.github/workflows/main-client.yml b/.github/workflows/main-client.yml index 4ddd9f4..ae30375 100644 --- a/.github/workflows/main-client.yml +++ b/.github/workflows/main-client.yml @@ -1,4 +1,4 @@ -name: Frontend Build & Push +name: Frontend Main on: workflow_dispatch: @@ -19,7 +19,7 @@ on: jobs: build-and-push: - name: Frontend Build & Push + name: Build & Push runs-on: ubuntu-latest permissions: contents: write diff --git a/.github/workflows/main-server.yml b/.github/workflows/main-server.yml index 66ef8e3..1b78f28 100644 --- a/.github/workflows/main-server.yml +++ b/.github/workflows/main-server.yml @@ -1,4 +1,4 @@ -name: Backend Build & Push +name: Backend Main on: workflow_dispatch: @@ -13,7 +13,7 @@ on: jobs: build-and-push: - name: Backend Build & Push + name: Build & Push runs-on: ubuntu-latest permissions: contents: write diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index a7b91bf..a36ff6b 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -1,11 +1,11 @@ -name: Backend CI +name: Backend PR on: workflow_dispatch: - # run for all pushes, not only main - push: + pull_request: + types: [opened, synchronize, reopened] branches: - - '**' + - 'main' paths: - 'server/**/*.java' - 'server/**/*.xml' @@ -14,7 +14,7 @@ on: jobs: run-checks: - name: Backend CI + name: Checks runs-on: ubuntu-latest permissions: contents: read @@ -31,6 +31,7 @@ jobs: distribution: 'temurin' java-version: '25' cache: 'maven' + cache-dependency-path: 'server/pom.xml' - name: Run Check Style working-directory: ./server @@ -43,3 +44,57 @@ jobs: - name: Run tests working-directory: ./server run: ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml + + build-and-push: + name: Build & Push + runs-on: ubuntu-latest + needs: ["run-checks"] + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set lowercase repo name + id: repo + run: echo "name=${GITHUB_REPOSITORY,,}" >> $GITHUB_OUTPUT + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + cache-dependency-path: 'server/pom.xml' + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache Buildpack layers + uses: actions/cache@v4 + with: + path: | + ~/.cache/reproducible-builds + key: ${{ runner.os }}-buildpack-${{ hashFiles('server/pom.xml') }} + restore-keys: | + ${{ runner.os }}-buildpack- + + - name: Build Docker image with Spring Boot + working-directory: ./server + run: | + ./mvnw -Pnative -DskipTests spring-boot:build-image \ + -Dspring-boot.build-image.imageName=ghcr.io/rmcampos/tasknote/api:latest \ + -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:latest + + - name: Tag and push Docker image + run: | + docker tag ghcr.io/${{ steps.repo.outputs.name }}/api:latest ghcr.io/${{ steps.repo.outputs.name }}/api:candidate + docker push ghcr.io/${{ steps.repo.outputs.name }}/api:candidate diff --git a/server/.mvn/wrapper/maven-wrapper.properties b/server/.mvn/wrapper/maven-wrapper.properties index 8f96f52..c595b00 100644 --- a/server/.mvn/wrapper/maven-wrapper.properties +++ b/server/.mvn/wrapper/maven-wrapper.properties @@ -1,19 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -wrapperVersion=3.3.2 +wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip diff --git a/server/mvnw b/server/mvnw index d7c358e..bd8896b 100755 --- a/server/mvnw +++ b/server/mvnw @@ -8,7 +8,7 @@ # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # -# https://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Optional ENV vars # ----------------- @@ -105,14 +105,17 @@ trim() { printf "%s" "${1}" | tr -d '[:space:]' } +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do case "${key-}" in distributionUrl) distributionUrl=$(trim "${value-}") ;; distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in maven-mvnd-*bin.*) @@ -130,7 +133,7 @@ maven-mvnd-*bin.*) distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" ;; maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME @@ -227,7 +230,7 @@ if [ -n "${distributionSha256Sum-}" ]; then echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then distributionSha256Result=true fi elif command -v shasum >/dev/null; then @@ -252,8 +255,41 @@ if command -v unzip >/dev/null; then else tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" diff --git a/server/mvnw.cmd b/server/mvnw.cmd index 6f779cf..5761d94 100644 --- a/server/mvnw.cmd +++ b/server/mvnw.cmd @@ -1,149 +1,189 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" -} -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/server/pom.xml b/server/pom.xml index a1733f2..2895343 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 4.0.3 + 4.0.5 @@ -49,6 +49,12 @@ ${project.build.directory}/coverage-reports ${maven.build.timestamp} yyyy-MM-dd HH:mm:ss + 3.5.5 + 3.5.5 + 0.8.14 + 3.6.0 + 4.0.3 + 0.12.6 @@ -143,17 +149,17 @@ io.jsonwebtoken jjwt-api - 0.12.6 + ${jjwt.version} io.jsonwebtoken jjwt-impl - 0.12.6 + ${jjwt.version} io.jsonwebtoken jjwt-gson - 0.12.6 + ${jjwt.version} @@ -181,7 +187,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.5.5 + ${failsafe.version} integration-tests @@ -203,7 +209,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.5 + ${surefire.version} @{argLine} -Xmx1024m ${skip.unit.tests} @@ -215,7 +221,7 @@ org.jacoco jacoco-maven-plugin - 0.8.14 + ${jacoco.version} ${jacoco.skip} @@ -324,7 +330,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.6.0 + ${checkstyle.version} com.puppycrawl.tools @@ -358,7 +364,8 @@ org.springframework.boot - spring-boot-maven-plugin + spring-boot-maven-plugin + ${springboot.version} ghcr.io/rmcampos/tasknote/api:latest diff --git a/terraform-stg/main.tf b/terraform-stg/main.tf new file mode 100644 index 0000000..2e4e976 --- /dev/null +++ b/terraform-stg/main.tf @@ -0,0 +1,382 @@ +terraform { + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.0.0" + } + } + + backend "s3" { + bucket = "tasknote-stg" + key = "kubernetes/terraform.tfstate" + region = "auto" + endpoints = { s3 = "https://d17eb09b6bce2f90e16e800bb2a6baf9.r2.cloudflarestorage.com" } + skip_credentials_validation = true + skip_region_validation = true + skip_requesting_account_id = true + skip_metadata_api_check = true + skip_s3_checksum = true + } +} + +provider "kubernetes" { + config_path = "~/.kube/config" +} + +variable "db_user" { + type = string + sensitive = true +} + +variable "db_password" { + type = string + sensitive = true +} + +variable "db_name" { + type = string + sensitive = true +} + +variable "security_key" { + type = string + sensitive = true +} + +variable "mailgun_apikey" { + type = string + sensitive = true +} + +variable "cors_allowed_origins" { + type = string + default = "https://tasknote-stg.darkroasted.vps-kinghost.net" +} + +variable "root_log_level" { + type = string + default = "INFO" +} + +variable "backend_image" { + type = string + default = "ghcr.io/rmcampos/tasknote/api:candidate" +} + +variable "frontend_image" { + type = string + default = "ghcr.io/rmcampos/tasknote/app:candidate" +} + +resource "kubernetes_namespace_v1" "tasknote_stg" { + metadata { + name = "tasknote-stg" + } +} + +resource "kubernetes_secret_v1" "tasknote_stg_secrets" { + metadata { + name = "tasknote-stg-secrets" + namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name + } + + data = { + postgres_user = var.db_user + postgres_password = var.db_password + postgres_db = var.db_name + security_key = var.security_key + mailgun_apikey = var.mailgun_apikey + } +} + +resource "kubernetes_persistent_volume_claim_v1" "tasknote_stg_db_data" { + metadata { + name = "postgres-data-pvc" + namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name + } + spec { + access_modes = ["ReadWriteOnce"] + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_deployment_v1" "tasknote_stg_db" { + metadata { + name = "tasknote-stg-db" + namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name + } + spec { + replicas = 1 + selector { match_labels = { app = "tasknote-stg-db" } } + template { + metadata { labels = { app = "tasknote-stg-db" } } + spec { + container { + image = "postgres:15.8-bookworm" + name = "postgres" + volume_mount { + name = "postgres-storage" + mount_path = "/var/lib/postgresql/data" + } + env { + name = "POSTGRES_USER" + value_from { + secret_key_ref { + name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name + key = "postgres_user" + } + } + } + env { + name = "POSTGRES_PASSWORD" + value_from { + secret_key_ref { + name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name + key = "postgres_password" + } + } + } + env { + name = "POSTGRES_DB" + value_from { + secret_key_ref { + name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name + key = "postgres_db" + } + } + } + port { container_port = 5432 } + } + volume { + name = "postgres-storage" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim_v1.tasknote_stg_db_data.metadata[0].name + } + } + } + } + } +} + +resource "kubernetes_service_v1" "tasknote_stg_db_svc" { + metadata { + name = "tasknote-stg-db-svc" + namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name + } + spec { + selector = { app = "tasknote-stg-db" } + port { port = 5432 } + type = "ClusterIP" + } +} + +resource "kubernetes_deployment_v1" "tasknote_stg_backend" { + metadata { + name = "tasknote-stg-backend" + namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name + } + spec { + replicas = 1 + selector { match_labels = { app = "tasknote-stg-backend" } } + template { + metadata { + labels = { app = "tasknote-stg-backend" } + annotations = { + "timestamp" = timestamp() + } + } + spec { + container { + image = var.backend_image + name = "backend" + image_pull_policy = "Always" + env { + name = "POSTGRES_DB" + value_from { + secret_key_ref { + name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name + key = "postgres_db" + } + } + } + env { + name = "POSTGRES_HOST" + value = "tasknote-stg-db-svc" + } + env { + name = "POSTGRES_USER" + value_from { + secret_key_ref { + name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name + key = "postgres_user" + } + } + } + env { + name = "POSTGRES_PASSWORD" + value_from { + secret_key_ref { + name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name + key = "postgres_password" + } + } + } + env { + name = "POSTGRES_PORT" + value = "5432" + } + env { + name = "CORS_ALLOWED_ORIGINS" + value = var.cors_allowed_origins + } + env { + name = "SERVER_SERVLET_CONTEXT_PATH" + value = "/" + } + env { + name = "ROOT_LOG_LEVEL" + value = var.root_log_level + } + env { + name = "SECURITY_KEY" + value_from { + secret_key_ref { + name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name + key = "security_key" + } + } + } + env { + name = "TARGET_ENV" + value = "production" + } + env { + name = "MAILGUN_APIKEY" + value_from { + secret_key_ref { + name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name + key = "mailgun_apikey" + } + } + } + resources { + limits = { memory = "256Mi", cpu = "500m" } + requests = { memory = "256Mi", cpu = "250m" } + } + } + } + } + } +} + +resource "kubernetes_service_v1" "tasknote_stg_backend_svc" { + metadata { + name = "tasknote-stg-backend-svc" + namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name + } + spec { + selector = { app = "tasknote-stg-backend" } + port { + port = 8585 + target_port = 8585 + } + } +} + +resource "kubernetes_deployment_v1" "tasknote_stg_frontend" { + metadata { + name = "tasknote-stg-frontend" + namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name + } + spec { + replicas = 1 + selector { match_labels = { app = "tasknote-stg-app" } } + template { + metadata { + labels = { app = "tasknote-stg-app" } + annotations = { + "timestamp" = timestamp() + } + } + spec { + container { + image = var.frontend_image + name = "frontend" + image_pull_policy = "Always" + port { container_port = 5000 } + env { + name = "VITE_BACKEND_SERVER" + value = "https://tasknoteapi-stg.darkroasted.vps-kinghost.net" + } + } + } + } + } +} + +resource "kubernetes_service_v1" "tasknote_stg_frontend_svc" { + metadata { + name = "tasknote-stg-frontend-svc" + namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name + } + spec { + selector = { app = "tasknote-stg-app" } + port { + port = 5000 + target_port = 5000 + } + type = "ClusterIP" + } +} + +# Unified Ingress for App and API +resource "kubernetes_ingress_v1" "tasknote_stg_ingress" { + metadata { + name = "tasknote-stg-ingress" + namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name + annotations = { + "kubernetes.io/ingress.class" = "traefik" + "cert-manager.io/cluster-issuer" = "letsencrypt-prod" + } + } + spec { + tls { + hosts = ["tasknote-stg.darkroasted.vps-kinghost.net", "tasknoteapi-stg.darkroasted.vps-kinghost.net"] + secret_name = "tasknote-stg-tls-certs" + } + rule { + host = "tasknote-stg.darkroasted.vps-kinghost.net" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.tasknote_stg_frontend_svc.metadata[0].name + port { number = 5000 } + } + } + } + } + } + rule { + host = "tasknoteapi-stg.darkroasted.vps-kinghost.net" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.tasknote_stg_backend_svc.metadata[0].name + port { number = 8585 } + } + } + } + } + } + } +} diff --git a/tools/check-be-deps.sh b/tools/check-be-deps.sh new file mode 100755 index 0000000..30325fd --- /dev/null +++ b/tools/check-be-deps.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +CURRENT_DIR=$(pwd) + +if [ ! -f "$CURRENT_DIR/pom.xml" ]; then + cd "$CURRENT_DIR/server" +fi + +# +# Get latest version of the `maven-failsafe-plugin` plugin +# +LATEST_FAILSAFE=$( + curl -s https://repo1.maven.org/maven2/org/apache/maven/plugins/maven-failsafe-plugin/maven-metadata.xml \ +| grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' \ +| sed -E 's/<\/?version>//g' \ +| sort -V \ +| tail -n 1 +) + +# Get current version from pom.xml, reading from the `failsafe.version` property +CURRENT_FAILSAFE=$(./mvnw help:evaluate -Dexpression=failsafe.version -q -DforceStdout 2>/dev/null) + +if [ "$LATEST_FAILSAFE" != "$CURRENT_FAILSAFE" ]; then + echo "The maven-failsafe-plugin is outdated. Current version: $CURRENT_FAILSAFE, Latest version: $LATEST_FAILSAFE" + exit 1 +else + echo "The maven-failsafe-plugin is up to date. Current version: $CURRENT_FAILSAFE" +fi + + + +# +# Get latest version of the `maven-surefire-plugin` plugin +# +LATEST_SUREFIRE=$( + curl -s https://repo1.maven.org/maven2/org/apache/maven/plugins/maven-surefire-plugin/maven-metadata.xml \ +| grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' \ +| sed -E 's/<\/?version>//g' \ +| sort -V \ +| tail -n 1 +) + +# Get current version from pom.xml, reading from the `surefire.version` property +CURRENT_SUREFIRE=$(./mvnw help:evaluate -Dexpression=surefire.version -q -DforceStdout 2>/dev/null) + +if [ "$LATEST_SUREFIRE" != "$CURRENT_SUREFIRE" ]; then + echo "The maven-surefire-plugin is outdated. Current version: $CURRENT_SUREFIRE, Latest version: $LATEST_SUREFIRE" + exit 1 +else + echo "The maven-surefire-plugin is up to date. Current version: $CURRENT_SUREFIRE" +fi + + + +# +# Get latest version of the `maven-jacoco-plugin` plugin +# +LATEST_JACOCO=$( + curl -s https://repo1.maven.org/maven2/org/jacoco/jacoco-maven-plugin/maven-metadata.xml \ +| grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' \ +| sed -E 's/<\/?version>//g' \ +| sort -V \ +| tail -n 1 +) + +# Get current version from pom.xml, reading from the `jacoco.version` property +CURRENT_JACOCO=$(./mvnw help:evaluate -Dexpression=jacoco.version -q -DforceStdout 2>/dev/null) +if [ "$LATEST_JACOCO" != "$CURRENT_JACOCO" ]; then + echo "The maven-jacoco-plugin is outdated. Current version: $CURRENT_JACOCO, Latest version: $LATEST_JACOCO" + exit 1 +else + echo "The maven-jacoco-plugin is up to date. Current version: $CURRENT_JACOCO" +fi + + +# -- +# +# Get latest version of the `maven-checkstyle-plugin` plugin +# +LATEST_CHECKSTYLE=$( + curl -s https://repo1.maven.org/maven2/org/apache/maven/plugins/maven-checkstyle-plugin/maven-metadata.xml \ +| grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' \ +| sed -E 's/<\/?version>//g' \ +| sort -V \ +| tail -n 1 +) + +# Get current version from pom.xml, reading from the `checkstyle.version` property +CURRENT_CHECKSTYLE=$(./mvnw help:evaluate -Dexpression=checkstyle.version -q -DforceStdout 2> /dev/null) +if [ "$LATEST_CHECKSTYLE" != "$CURRENT_CHECKSTYLE" ]; then + echo "The maven-checksytle-plugin is outdated. Current version: $CURRENT_CHECKSTYLE, Latest version: $LATEST_CHECKSTYLE" + exit 1 +else + echo "The maven-cehckstyle-plugin is up to date. Current version: $CURRENT_CHECKSTYLE" +fi + + +# -- + +# +# Get latest version of the Spring Boot Starter Web plugin +# +LATEST_SPRINGBOOT=$( + curl -s https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-maven-plugin/maven-metadata.xml \ +| grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' \ +| sed -E 's/<\/?version>//g' \ +| sort -V \ +| tail -n 1 +) + +# Get current version from pom.xml, reading from the `springboot.version` property +CURRENT_SPRINGBOOT=$(./mvnw help:evaluate -Dexpression=springboot.version -q -DforceStdout 2>/dev/null) +if [ "$LATEST_SPRINGBOOT" != "$CURRENT_SPRINGBOOT" ]; then + echo "Spring Boot is outdated. Current version: $CURRENT_SPRINGBOOT, Latest version: $LATEST_SPRINGBOOT" + exit 1 +else + echo "Spring Boot is up to date. Current version: $CURRENT_SPRINGBOOT" +fi +