From a37951628d64c52bf5e546adc2c5afcf608dc86d Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Mon, 6 Apr 2026 16:00:45 -0300 Subject: [PATCH 01/14] chore: bump spring version to 4.0.5 --- server/.mvn/wrapper/maven-wrapper.properties | 20 +- server/mvnw | 52 ++- server/mvnw.cmd | 338 +++++++++++-------- server/pom.xml | 25 +- tools/check-be-deps.sh | 119 +++++++ 5 files changed, 370 insertions(+), 184 deletions(-) create mode 100755 tools/check-be-deps.sh 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/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 + From 9609f7809584d4f56fd476d3566c4ef566d2a447 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Mon, 6 Apr 2026 16:04:18 -0300 Subject: [PATCH 02/14] feat: add docker build to CI workflow --- .github/workflows/server-ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index a7b91bf..b9e0f1f 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -43,3 +43,11 @@ jobs: - name: Run tests working-directory: ./server run: ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml + + - 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 + From d7ebc784ac18ac91f3da6ffaeddec67515985384 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Mon, 6 Apr 2026 19:35:07 -0300 Subject: [PATCH 03/14] feat: add staging deploy - wip --- .github/workflows/server-ci.yml | 58 ++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index b9e0f1f..ce55b4e 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -44,10 +44,66 @@ jobs: working-directory: ./server run: ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml + deploy-to-staging: + name: Deploy to Staging + 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' + + - name: Defines image tag + id: version + working-directory: ./server + run: | + # Extract current version from pom.xml + CURRENT_VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout 2>/dev/null) + echo "Current version: ${CURRENT_VERSION}" + + # Increment version + NEW_VERSION=$((CURRENT_VERSION + 1)) + echo "New version candidate: ${NEW_VERSION}" + + # Set the new version with runner number + FINAL_TAG="$NEW_VERSION.${{ github.run_number }}" + echo "Final TAG version: ${FINAL_TAG}" + + # Output for later steps + echo "version=${FINAL_TAG}" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - 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.imageName=ghcr.io/rmcampos/tasknote/api:candidate \ -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:candidate ghcr.io/${{ steps.repo.outputs.name }}/api:${{ steps.version.outputs.version }} + docker push ghcr.io/${{ steps.repo.outputs.name }}/api:candidate + docker push ghcr.io/${{ steps.repo.outputs.name }}/api:${{ steps.version.outputs.version }} \ No newline at end of file From 49e0e71a4fbdc609ecde79bd8f2cbdbd3e82d06d Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 16:43:57 -0300 Subject: [PATCH 04/14] fix: change triggers to include PR changes --- .github/workflows/server-ci.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index ce55b4e..fcbde8f 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -6,11 +6,9 @@ on: push: branches: - '**' - paths: - - 'server/**/*.java' - - 'server/**/*.xml' - - 'server/pom.xml' - - '.github/workflows/server-ci.yml' + pull_request: + branches: + - main jobs: run-checks: From f98770c2ff8160b8567cf02f88f4849ba229d050 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 17:36:58 -0300 Subject: [PATCH 05/14] feat: add server ci and deploy to stg --- .github/workflows/server-ci.yml | 178 ++++++++++++--- terraform-stg/main.tf | 370 ++++++++++++++++++++++++++++++++ 2 files changed, 519 insertions(+), 29 deletions(-) create mode 100644 terraform-stg/main.tf diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index fcbde8f..5b73440 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -2,13 +2,15 @@ name: Backend CI on: workflow_dispatch: - # run for all pushes, not only main - push: - branches: - - '**' pull_request: + types: [opened, synchronize, reopened] branches: - - main + - 'main' + paths: + - 'server/**/*.java' + - 'server/**/*.xml' + - 'server/pom.xml' + - '.github/workflows/server-ci.yml' jobs: run-checks: @@ -29,6 +31,7 @@ jobs: distribution: 'temurin' java-version: '25' cache: 'maven' + cache-dependency-path: 'server/pom.xml' - name: Run Check Style working-directory: ./server @@ -42,8 +45,8 @@ jobs: working-directory: ./server run: ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml - deploy-to-staging: - name: Deploy to Staging + build-and-push: + name: Build & Push runs-on: ubuntu-latest needs: ["run-checks"] permissions: @@ -66,25 +69,7 @@ jobs: distribution: 'temurin' java-version: '25' cache: 'maven' - - - name: Defines image tag - id: version - working-directory: ./server - run: | - # Extract current version from pom.xml - CURRENT_VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout 2>/dev/null) - echo "Current version: ${CURRENT_VERSION}" - - # Increment version - NEW_VERSION=$((CURRENT_VERSION + 1)) - echo "New version candidate: ${NEW_VERSION}" - - # Set the new version with runner number - FINAL_TAG="$NEW_VERSION.${{ github.run_number }}" - echo "Final TAG version: ${FINAL_TAG}" - - # Output for later steps - echo "version=${FINAL_TAG}" >> $GITHUB_OUTPUT + cache-dependency-path: 'server/pom.xml' - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -92,16 +77,151 @@ jobs: 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:candidate \ + -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:candidate ghcr.io/${{ steps.repo.outputs.name }}/api:${{ steps.version.outputs.version }} + 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 - docker push ghcr.io/${{ steps.repo.outputs.name }}/api:${{ steps.version.outputs.version }} \ No newline at end of file + + terraform-plan-stg: + name: Plan changs to staging + runs-on: ubuntu-latest + outputs: + no_changes: ${{ steps.check-changes.outputs.no_changes }} + needs: ["build-and-push"] + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v6 + 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: 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/terraform-stg/main.tf b/terraform-stg/main.tf new file mode 100644 index 0000000..8c95130 --- /dev/null +++ b/terraform-stg/main.tf @@ -0,0 +1,370 @@ +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 = "pull 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" } } + spec { + container { + image = var.backend_image + name = "backend" + 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" } } + spec { + container { + image = var.frontend_image + name = "frontend" + 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 } + } + } + } + } + } + } +} From e61f24032cc70bb94532dee275ec5eef03167cc9 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 17:58:13 -0300 Subject: [PATCH 06/14] chore: add app build candidate --- .github/workflows/build-app-candidate.yml | 48 +++++++++++++++++++++++ .github/workflows/client-ci.yml | 44 ++++++++++++++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-app-candidate.yml diff --git a/.github/workflows/build-app-candidate.yml b/.github/workflows/build-app-candidate.yml new file mode 100644 index 0000000..0d4dc17 --- /dev/null +++ b/.github/workflows/build-app-candidate.yml @@ -0,0 +1,48 @@ +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: 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 }}" diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml index 27908be..65afd03 100644 --- a/.github/workflows/client-ci.yml +++ b/.github/workflows/client-ci.yml @@ -18,7 +18,7 @@ on: - '.github/workflows/client-ci.yml' jobs: - build-and-push: + run-checks: name: Frontend CI runs-on: ubuntu-latest permissions: @@ -51,3 +51,45 @@ jobs: - name: Run tests run: npm run test:no-watch working-directory: ./client + + build-and-push: + name: Build & Push + runs-on: ubuntu-latest + 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=candidade + + - 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 }}" From 1b7bb7e5c69b45d853fa6c2c44fbeac903d0eb2a Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 18:00:58 -0300 Subject: [PATCH 07/14] remove from branch, get from main --- .github/workflows/build-app-candidate.yml | 48 ----------------------- 1 file changed, 48 deletions(-) delete mode 100644 .github/workflows/build-app-candidate.yml diff --git a/.github/workflows/build-app-candidate.yml b/.github/workflows/build-app-candidate.yml deleted file mode 100644 index 0d4dc17..0000000 --- a/.github/workflows/build-app-candidate.yml +++ /dev/null @@ -1,48 +0,0 @@ -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: 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 }}" From 4032654b7818c3faac88d72287788dfcc33d920a Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 18:03:45 -0300 Subject: [PATCH 08/14] fix: build app candidate --- .github/workflows/build-app-candidate.yml | 48 ----------------------- 1 file changed, 48 deletions(-) diff --git a/.github/workflows/build-app-candidate.yml b/.github/workflows/build-app-candidate.yml index 0d1e778..0d4dc17 100644 --- a/.github/workflows/build-app-candidate.yml +++ b/.github/workflows/build-app-candidate.yml @@ -1,53 +1,5 @@ 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: 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: From 3910381065ba3954db0d1f32fdf877be95843920 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 18:10:51 -0300 Subject: [PATCH 09/14] fix: build app candidate --- .github/workflows/build-app-candidate.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-app-candidate.yml b/.github/workflows/build-app-candidate.yml index 0d4dc17..462881b 100644 --- a/.github/workflows/build-app-candidate.yml +++ b/.github/workflows/build-app-candidate.yml @@ -36,13 +36,22 @@ jobs: 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 }} From 4677ebc4da4a7cb0c19a08d57811593a835ceb3f Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 18:21:06 -0300 Subject: [PATCH 10/14] fix: fe changes in the app --- .github/workflows/build-app-candidate.yml | 2 +- terraform-stg/main.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-app-candidate.yml b/.github/workflows/build-app-candidate.yml index 462881b..b8a2e2c 100644 --- a/.github/workflows/build-app-candidate.yml +++ b/.github/workflows/build-app-candidate.yml @@ -34,7 +34,7 @@ jobs: with: images: ghcr.io/${{ github.repository }}/app tags: | - type=raw,value=candidade + type=raw,value=candidate - name: Generate version tag id: version diff --git a/terraform-stg/main.tf b/terraform-stg/main.tf index 8c95130..801eedf 100644 --- a/terraform-stg/main.tf +++ b/terraform-stg/main.tf @@ -60,7 +60,7 @@ variable "root_log_level" { variable "backend_image" { type = string - default = "pull ghcr.io/rmcampos/tasknote/api:candidate" + default = "ghcr.io/rmcampos/tasknote/api:candidate" } variable "frontend_image" { From e5edc3a1d9eb4a4d1e8cc33496a4a62a7d7ed9b7 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 18:28:09 -0300 Subject: [PATCH 11/14] fix: app ci workflow dispatch --- .github/workflows/client-ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml index 65afd03..52ddc18 100644 --- a/.github/workflows/client-ci.yml +++ b/.github/workflows/client-ci.yml @@ -2,9 +2,10 @@ name: Frontend CI on: workflow_dispatch: - push: + pull_request: + types: [opened, synchronize, reopened] branches: - - '**' + - 'main' paths: - 'client/**/*.html' - 'client/**/*.png' @@ -81,7 +82,7 @@ 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 From b65bd56add79c7210a1241a526fa3f5fe4575227 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 18:38:40 -0300 Subject: [PATCH 12/14] chore: improve PR ci and cd --- .github/workflows/client-ci.yml | 13 ++- .github/workflows/deploy-stg.yml | 136 +++++++++++++++++++++++++++++++ .github/workflows/deploy.yml | 2 +- .github/workflows/server-ci.yml | 127 ----------------------------- 4 files changed, 148 insertions(+), 130 deletions(-) create mode 100644 .github/workflows/deploy-stg.yml diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml index 52ddc18..6f881f1 100644 --- a/.github/workflows/client-ci.yml +++ b/.github/workflows/client-ci.yml @@ -84,13 +84,22 @@ jobs: 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: 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/deploy-stg.yml b/.github/workflows/deploy-stg.yml new file mode 100644 index 0000000..2ec6466 --- /dev/null +++ b/.github/workflows/deploy-stg.yml @@ -0,0 +1,136 @@ +name: Deploy to staging + +on: + workflow_dispatch: + workflow_run: + workflows: [ "Backend CI", "Frontend CI" ] + 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..b97e2dd 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: diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index 5b73440..c1c3abc 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -98,130 +98,3 @@ jobs: 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 - - terraform-plan-stg: - name: Plan changs to staging - runs-on: ubuntu-latest - outputs: - no_changes: ${{ steps.check-changes.outputs.no_changes }} - needs: ["build-and-push"] - permissions: - contents: read - steps: - - name: Checkout code - uses: actions/checkout@v6 - 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: 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 From 5f6d7c7c0d187e61898d6cfa2a10bd01ea9bb2cb Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 18:48:57 -0300 Subject: [PATCH 13/14] feat: make deployments always happen for PRs --- .github/workflows/client-ci.yml | 1 + terraform-stg/main.tf | 24 ++++++++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml index 6f881f1..93c540d 100644 --- a/.github/workflows/client-ci.yml +++ b/.github/workflows/client-ci.yml @@ -56,6 +56,7 @@ jobs: build-and-push: name: Build & Push runs-on: ubuntu-latest + needs: ["run-checks"] permissions: contents: write packages: write diff --git a/terraform-stg/main.tf b/terraform-stg/main.tf index 801eedf..2e4e976 100644 --- a/terraform-stg/main.tf +++ b/terraform-stg/main.tf @@ -183,11 +183,17 @@ resource "kubernetes_deployment_v1" "tasknote_stg_backend" { replicas = 1 selector { match_labels = { app = "tasknote-stg-backend" } } template { - metadata { labels = { app = "tasknote-stg-backend" } } + metadata { + labels = { app = "tasknote-stg-backend" } + annotations = { + "timestamp" = timestamp() + } + } spec { container { - image = var.backend_image - name = "backend" + image = var.backend_image + name = "backend" + image_pull_policy = "Always" env { name = "POSTGRES_DB" value_from { @@ -290,11 +296,17 @@ resource "kubernetes_deployment_v1" "tasknote_stg_frontend" { replicas = 1 selector { match_labels = { app = "tasknote-stg-app" } } template { - metadata { labels = { app = "tasknote-stg-app" } } + metadata { + labels = { app = "tasknote-stg-app" } + annotations = { + "timestamp" = timestamp() + } + } spec { container { - image = var.frontend_image - name = "frontend" + image = var.frontend_image + name = "frontend" + image_pull_policy = "Always" port { container_port = 5000 } env { name = "VITE_BACKEND_SERVER" From ac3eee896d13d31a8a5de2267b31faf04d7f0e49 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 7 Apr 2026 18:57:44 -0300 Subject: [PATCH 14/14] feat: improve workflow names --- .github/workflows/client-ci.yml | 4 ++-- .github/workflows/deploy-stg.yml | 2 +- .github/workflows/deploy.yml | 2 +- .github/workflows/main-client.yml | 4 ++-- .github/workflows/main-server.yml | 4 ++-- .github/workflows/server-ci.yml | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml index 93c540d..2567b91 100644 --- a/.github/workflows/client-ci.yml +++ b/.github/workflows/client-ci.yml @@ -1,4 +1,4 @@ -name: Frontend CI +name: Frontend PR on: workflow_dispatch: @@ -20,7 +20,7 @@ on: jobs: run-checks: - name: Frontend CI + name: Checks runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index 2ec6466..6347f44 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -3,7 +3,7 @@ name: Deploy to staging on: workflow_dispatch: workflow_run: - workflows: [ "Backend CI", "Frontend CI" ] + workflows: [ "Backend PR", "Frontend PR" ] types: [ completed ] jobs: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b97e2dd..f1e34a5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -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 c1c3abc..a36ff6b 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -1,4 +1,4 @@ -name: Backend CI +name: Backend PR on: workflow_dispatch: @@ -14,7 +14,7 @@ on: jobs: run-checks: - name: Backend CI + name: Checks runs-on: ubuntu-latest permissions: contents: read