Skip to content
315 changes: 92 additions & 223 deletions .github/workflows/pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ on:
- Debug
- Release

permissions:
contents: read

jobs:
prepare_linux:
name: 🐧 Prepare Linux
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
timeout-minutes: 15
outputs:
restoreCacheKey: ${{ steps.dotnet-restore.outputs.restoreCacheKey }}
Expand Down Expand Up @@ -61,149 +64,91 @@ jobs:
with:
useRestoreCache: true

build:
name: 🛠️ Build
runs-on: ubuntu-22.04
timeout-minutes: 15
strategy:
matrix:
configuration: [Debug, Release]
framework: [net9.0, net8.0, netstandard2.1, netstandard2.0]
needs: [prepare_linux]
prepare_test:
name: 📜 Prepare Test
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
version: ${{ steps.minver-calculate.outputs.version }}
projects: ${{ steps.projects.outputs.result }}
testProjects: ${{ steps.test-projects.outputs.result }}
json: ${{ steps.test-projects.outputs.result }}
steps:
- name: Checkout
uses: codebeltnet/git-checkout@v1

- name: Install .NET
uses: codebeltnet/install-dotnet@v1
with:
includePreview: true

- name: Install MinVer
uses: codebeltnet/dotnet-tool-install-minver@v1

- id: minver-calculate
name: Calculate Version
uses: codebeltnet/minver-calculate@v2

- name: Download cuemon.snk file
uses: codebeltnet/gcp-download-file@v1
with:
serviceAccountKey: ${{ secrets.GCP_TOKEN }}
bucketName: ${{ secrets.GCP_BUCKETNAME }}
objectName: cuemon.snk

- id: projects
name: Set environment variable for projects
run: |
if [ "${{ matrix.framework }}" == "netstandard2.1" ]; then
echo "result=src/**/Cuemon.Extensions.IO.csproj src/**/Cuemon.IO.csproj" >> $GITHUB_OUTPUT
elif [ "${{ matrix.framework }}" == "netstandard2.0" ]; then
projects=(
"src/**/Cuemon.Core.csproj"
"src/**/Cuemon.Data.csproj"
"src/**/Cuemon.Data.Integrity.csproj"
"src/**/Cuemon.Data.SqlClient.csproj"
"src/**/Cuemon.Diagnostics.csproj"
"src/**/Cuemon.Extensions.Collections.Generic.csproj"
"src/**/Cuemon.Extensions.Collections.Specialized.csproj"
"src/**/Cuemon.Extensions.Core.csproj"
"src/**/Cuemon.Extensions.Data.csproj"
"src/**/Cuemon.Extensions.Data.Integrity.csproj"
"src/**/Cuemon.Extensions.DependencyInjection.csproj"
"src/**/Cuemon.Extensions.Diagnostics.csproj"
"src/**/Cuemon.Extensions.Hosting.csproj"
"src/**/Cuemon.Extensions.IO.csproj"
"src/**/Cuemon.Extensions.Net.csproj"
"src/**/Cuemon.Extensions.Reflection.csproj"
"src/**/Cuemon.Extensions.Runtime.Caching.csproj"
"src/**/Cuemon.Extensions.Text.csproj"
"src/**/Cuemon.Extensions.Text.Json.csproj"
"src/**/Cuemon.Extensions.Threading.csproj"
"src/**/Cuemon.Extensions.Xml.csproj"
"src/**/Cuemon.IO.csproj"
"src/**/Cuemon.Net.csproj"
"src/**/Cuemon.Resilience.csproj"
"src/**/Cuemon.Runtime.Caching.csproj"
"src/**/Cuemon.Security.Cryptography.csproj"
"src/**/Cuemon.Threading.csproj"
"src/**/Cuemon.Xml.csproj"
)
echo "result=$(IFS=' '; echo "${projects[*]}")" >> $GITHUB_OUTPUT
else
echo "result=src/**/*.csproj" >> $GITHUB_OUTPUT
fi
shell: bash

- id: dotnet-build
name: Build for ${{ matrix.framework }} (${{ matrix.configuration }})
uses: codebeltnet/dotnet-build@v2
with:
projects: ${{ steps.projects.outputs.result }}
configuration: ${{ matrix.configuration }}
framework: ${{ matrix.framework }}
restoreCacheKey: ${{ needs.prepare_linux.outputs.restoreCacheKey }}

- id: test-projects
name: Generate matrix for test projects
uses: codebeltnet/shell-globbing@v1
with:
pattern: test/**/*.csproj
pattern: |
test/**/*.csproj
!test/**/Cuemon.Data.SqlClient.Tests.csproj

pack:
name: 📦 Pack
runs-on: ubuntu-22.04
timeout-minutes: 15
- name: JSON output
run: echo "${{ steps.test-projects.outputs.result }}"

build:
name: call-build
needs: [prepare_linux]
strategy:
matrix:
configuration: [Debug, Release]
needs: [prepare_linux, build]
steps:
- name: Checkout
uses: codebeltnet/git-checkout@v1
uses: codebeltnet/jobs-dotnet-build/.github/workflows/default.yml@v1
with:
configuration: ${{ matrix.configuration }}
restore-cache-key: ${{ needs.prepare_linux.outputs.restoreCacheKey }}
strong-name-key-filename: cuemon.snk
secrets:
GCP_TOKEN: ${{ secrets.GCP_TOKEN }}
GCP_BUCKETNAME: ${{ secrets.GCP_BUCKETNAME }}

- name: Install .NET
uses: codebeltnet/install-dotnet@v1
with:
includePreview: true

- name: Pack for ${{ matrix.configuration }}
uses: codebeltnet/dotnet-pack@v2
with:
configuration: ${{ matrix.configuration }}
uploadPackedArtifact: true
version: ${{ needs.build.outputs.version }}
restoreCacheKey: ${{ needs.prepare_linux.outputs.restoreCacheKey }}
pack:
name: call-pack
needs: [prepare_linux, build]
strategy:
matrix:
configuration: [Debug, Release]
uses: codebeltnet/jobs-dotnet-pack/.github/workflows/default.yml@v1
with:
configuration: ${{ matrix.configuration }}
upload-packed-artifact: true
version: ${{ needs.build.outputs.version }}
restore-cache-key: ${{ needs.prepare_linux.outputs.restoreCacheKey }}

test:
name: 🧪 Test
needs: [build, prepare_linux, prepare_windows]
name: call-test
needs: [build, prepare_test, prepare_linux, prepare_windows]
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, windows-2022]
configuration: [Debug, Release]
project: ${{ fromJson(needs.prepare_test.outputs.json) }}
uses: codebeltnet/jobs-dotnet-test/.github/workflows/default.yml@v1
with:
configuration: ${{ matrix.configuration }}
projects: ${{ matrix.project }}
restore-cache-key: ${{ matrix.os == 'Linux' && needs.prepare_linux.outputs.restoreCacheKey || needs.prepare_windows.outputs.restoreCacheKey }}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix OS-based cache key resolution
The expression

restore-cache-key: ${{ matrix.os == 'Linux' && needs.prepare_linux.outputs.restoreCacheKey || needs.prepare_windows.outputs.restoreCacheKey }}

will never match (matrix.os is ubuntu-24.04 or windows-2022). As a result, Linux runs use the Windows cache.
Apply this diff:

- restore-cache-key: ${{ matrix.os == 'Linux' && needs.prepare_linux.outputs.restoreCacheKey || needs.prepare_windows.outputs.restoreCacheKey }}
+ restore-cache-key: ${{ (startsWith(matrix.os, 'ubuntu') && needs.prepare_linux.outputs.restoreCacheKey) || needs.prepare_windows.outputs.restoreCacheKey }}

This ensures the correct cache key is selected per OS.

🤖 Prompt for AI Agents
In .github/workflows/pipelines.yml around lines 117 to 130, the
restore-cache-key expression incorrectly compares matrix.os to 'Linux', which
never matches since matrix.os values are 'ubuntu-24.04' or 'windows-2022'.
Update the condition to check if matrix.os starts with 'ubuntu' or equals
'ubuntu-24.04' to correctly select the Linux cache key; otherwise, use the
Windows cache key. This will ensure the proper cache key is used based on the
actual OS in the matrix.

integration_test:
name: ⚗️ Integration Test
needs: [build, prepare_test, prepare_linux]
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-2022]
configuration: [Debug, Release]
project: ${{ fromJson(needs.build.outputs.testProjects) }}
runs-on: ${{ matrix.os }}
project: [ test/**/Cuemon.Data.SqlClient.Tests.csproj ]
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Checkout
uses: codebeltnet/git-checkout@v1

- name: Install .NET
uses: codebeltnet/install-dotnet@v1
with:
includePreview: true
uses: codebeltnet/install-dotnet@v2

- name: Install .NET Tool - Report Generator
uses: codebeltnet/dotnet-tool-install-reportgenerator@v1

- name: Spin up SQL Server test dependency for ${{ matrix.configuration }} build
if: ${{ (runner.os == 'Linux' && contains(matrix.project, 'Cuemon.Data.SqlClient.Tests')) }}
uses: codebeltnet/docker-compose@v1
with:
command: up
Expand All @@ -212,133 +157,57 @@ jobs:
SA_PASSWORD: ${{ secrets.SA_PASSWORD }}

- name: Test with ${{ matrix.configuration }} build
if: ${{ !(runner.os == 'Windows' && contains(matrix.project, 'Cuemon.Data.SqlClient.Tests')) }}
uses: codebeltnet/dotnet-test@v3
with:
projects: ${{ matrix.project }}
configuration: ${{ matrix.configuration }}
restoreCacheKey: ${{ runner.os == 'Linux' && needs.prepare_linux.outputs.restoreCacheKey || needs.prepare_windows.outputs.restoreCacheKey }}
buildSwitches: ${{ contains(matrix.project, 'Cuemon.Extensions.Globalization.Tests') && '-p:SkipSignAssembly=false' || '-p:SkipSignAssembly=true' }}
testArguments: -- RunConfiguration.DisableAppDomain=true
restoreCacheKey: ${{ needs.prepare_linux.outputs.restoreCacheKey }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Input name casing mismatch on restore key
The integration_test step uses restoreCacheKey, but all other reusable workflows expect restore-cache-key (hyphenated). This will likely cause a missing-input failure.

Please update it to:

-          restoreCacheKey: ${{ needs.prepare_linux.outputs.restoreCacheKey }}
+          restore-cache-key: ${{ needs.prepare_linux.outputs.restoreCacheKey }}
🤖 Prompt for AI Agents
In .github/workflows/pipelines.yml at line 165, the input name for the restore
cache key is incorrectly cased as restoreCacheKey, but it should be
restore-cache-key with hyphens to match the expected input name in other
reusable workflows. Change restoreCacheKey to restore-cache-key to fix the input
name casing mismatch and prevent missing-input failures.

env:
CONNECTIONSTRINGS__ADVENTUREWORKS: ${{ secrets.DB_ADVENTUREWORKS }}

- name: Take down SQL Server test dependency for ${{ matrix.configuration }} build
if: ${{ (runner.os == 'Linux' && contains(matrix.project, 'Cuemon.Data.SqlClient.Tests')) }}
uses: codebeltnet/docker-compose@v1
with:
command: down

sonarcloud:
name: 🔬 Code Quality Analysis
needs: [prepare_linux, build, test]
runs-on: ubuntu-22.04
timeout-minutes: 25
steps:
- name: Checkout
uses: codebeltnet/git-checkout@v1

- name: Install .NET
uses: codebeltnet/install-dotnet@v1
with:
includePreview: true

- name: Install .NET Tool - Sonar Scanner
uses: codebeltnet/dotnet-tool-install-sonarscanner@v1

- name: Restore Dependencies
uses: codebeltnet/dotnet-restore@v2
with:
useRestoreCache: true
restoreCacheKey: ${{ needs.prepare_linux.outputs.restoreCacheKey }}

- name: Download cuemon.snk file
uses: codebeltnet/gcp-download-file@v1
with:
serviceAccountKey: ${{ secrets.GCP_TOKEN }}
bucketName: ${{ secrets.GCP_BUCKETNAME }}
objectName: cuemon.snk

- name: Run SonarCloud Analysis
uses: codebeltnet/sonarcloud-scan@v1
with:
token: ${{ secrets.SONAR_TOKEN }}
organization: geekle
projectKey: Cuemon
version: ${{ needs.build.outputs.version }}

- name: Build
uses: codebeltnet/dotnet-build@v2
with:
uploadBuildArtifact: false

- name: Finalize SonarCloud Analysis
uses: codebeltnet/sonarcloud-scan-finalize@v1
with:
token: ${{ secrets.SONAR_TOKEN }}
name: call-sonarcloud
needs: [build,test]
uses: codebeltnet/jobs-sonarcloud/.github/workflows/default.yml@v1
with:
organization: geekle
projectKey: Cuemon
version: ${{ needs.build.outputs.version }}
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

codecov:
name: 📊 Code Coverage Analysis
needs: [build, test]
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- name: Checkout
uses: codebeltnet/git-checkout@v1

- name: Run CodeCov Analysis
uses: codebeltnet/codecov-scan@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
repository: gimlichael/Cuemon
name: call-codecov
needs: [build,test]
uses: codebeltnet/jobs-codecov/.github/workflows/default.yml@v1
with:
repository: gimlichael/Cuemon
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

codeql:
name: 🛡️ Security Analysis
needs: [prepare_linux, build, test]
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- name: Checkout
uses: codebeltnet/git-checkout@v1

- name: Install .NET
uses: codebeltnet/install-dotnet@v1
with:
includePreview: true

- name: Restore Dependencies
uses: codebeltnet/dotnet-restore@v2
with:
useRestoreCache: true
restoreCacheKey: ${{ needs.prepare_linux.outputs.restoreCacheKey }}

- name: Prepare CodeQL SAST Analysis
uses: codebeltnet/codeql-scan@v1

- name: Download cuemon.snk file
uses: codebeltnet/gcp-download-file@v1
with:
serviceAccountKey: ${{ secrets.GCP_TOKEN }}
bucketName: ${{ secrets.GCP_BUCKETNAME }}
objectName: cuemon.snk

- name: Build
uses: codebeltnet/dotnet-build@v2
with:
uploadBuildArtifact: false

- name: Finalize CodeQL SAST Analysis
uses: codebeltnet/codeql-scan-finalize@v1
name: call-codeql
needs: [build,test]
uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v1
permissions:
security-events: write

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure CodeQL job retains read access
Currently, the codeql job’s permissions block lists only security-events: write. Because job-level permissions override the global ones, this removes the default contents: read, which CodeQL requires to scan the repository. Update it as follows:

 codeql:
   name: call-codeql
   needs: [build,test]
   uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v1
-  permissions:
-    security-events: write
+  permissions:
+    contents: read
+    security-events: write
+  secrets: inherit
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
codeql:
name: 🛡️ Security Analysis
needs: [prepare_linux, build, test]
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- name: Checkout
uses: codebeltnet/git-checkout@v1
- name: Install .NET
uses: codebeltnet/install-dotnet@v1
with:
includePreview: true
- name: Restore Dependencies
uses: codebeltnet/dotnet-restore@v2
with:
useRestoreCache: true
restoreCacheKey: ${{ needs.prepare_linux.outputs.restoreCacheKey }}
- name: Prepare CodeQL SAST Analysis
uses: codebeltnet/codeql-scan@v1
- name: Download cuemon.snk file
uses: codebeltnet/gcp-download-file@v1
with:
serviceAccountKey: ${{ secrets.GCP_TOKEN }}
bucketName: ${{ secrets.GCP_BUCKETNAME }}
objectName: cuemon.snk
- name: Build
uses: codebeltnet/dotnet-build@v2
with:
uploadBuildArtifact: false
- name: Finalize CodeQL SAST Analysis
uses: codebeltnet/codeql-scan-finalize@v1
name: call-codeql
needs: [build,test]
uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v1
permissions:
security-events: write
codeql:
name: call-codeql
needs: [build,test]
uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v1
permissions:
contents: read
security-events: write
secrets: inherit
🧰 Tools
🪛 YAMLlint (1.37.1)

[warning] 255-255: too few spaces after comma

(commas)

🤖 Prompt for AI Agents
In .github/workflows/pipelines.yml around lines 253 to 259, the codeql job's
permissions only specify 'security-events: write', which overrides and removes
the default 'contents: read' permission needed by CodeQL to scan the repository.
To fix this, add 'contents: read' alongside 'security-events: write' in the
permissions block to ensure CodeQL retains the necessary read access.

deploy:
if: github.event_name != 'pull_request'
name: 🚀 Deploy v${{ needs.build.outputs.version }}
runs-on: ubuntu-22.04
timeout-minutes: 15
needs: [build, pack, test, sonarcloud, codecov, codeql]
environment: Production
steps:
- uses: codebeltnet/nuget-push@v1
with:
token: ${{ secrets.NUGET_TOKEN }}
configuration: ${{ inputs.configuration == '' && 'Release' || inputs.configuration }}
name: call-nuget
needs: [build,pack,test,sonarcloud,codecov,codeql]
uses: codebeltnet/jobs-nuget-push/.github/workflows/default.yml@v1
with:
version: ${{ needs.build.outputs.version }}
environment: Production
configuration: ${{ inputs.configuration == '' && 'Release' || inputs.configuration }}
permissions:
contents: write
packages: write
secrets:
NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }}
Loading