diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 0fa400e..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Build -on: - pull_request: - branches: - - master - types: - - opened - - reopened - - edited - - synchronize - -jobs: - # Build source code - build: - runs-on: ubuntu-latest - strategy: - matrix: - dotnet-version: [8.0.x] - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - # Setup .NET SDK - - name: Set up .NET SDK ${{ matrix.dotnet-version }} - uses: actions/setup-dotnet@v1 - with: - dotnet-version: ${{ matrix.dotnet-version }} - - # Setup cake tool - - name: Setup cake tool - run: | - dotnet tool install --global Cake.Tool - - # Build the project - - name: Build project - if: success() - run: | - # Copy Licence - cp LICENSE NETCore.Keycloak.Client/ - - # Build project - dotnet cake build.cake --target=build diff --git a/.github/workflows/build_test_analyze.yml b/.github/workflows/build_test_analyze.yml new file mode 100644 index 0000000..cef08a5 --- /dev/null +++ b/.github/workflows/build_test_analyze.yml @@ -0,0 +1,62 @@ +name: Build test and analyze +on: + push: + branches: + - master + pull_request: + branches: + - master + types: + - opened + - reopened + - edited + - synchronize + +jobs: + # Build test and analyze source code + build_test_analyze: + runs-on: ubuntu-22.04 + strategy: + matrix: + java-version: [ 21 ] + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + # Setup OpenJDK + - name: Setup OpenJDK + uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: ${{ matrix.java-version }} + + # Install all required .NET SDK versions + - name: Install .NET SDKs (6.0, 7.0, 8.0) + uses: actions/setup-dotnet@v1 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + + # Install dependencies + - name: Install dependencies + run: | + sudo apt install -y make python3-pip python3-rpm python3-psycopg2 + pip install 'python-keycloak==3.3.0' --user + dotnet tool install --global dotnet-sonarscanner + dotnet tool install --global Cake.Tool + dotnet tool install --global JetBrains.dotCover.GlobalTool + + # Build test and analyze the project + - name: Build test and analyze the project + if: success() + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + # Copy Licence + cp LICENSE NETCore.Keycloak.Client/ + + # Build, test and analyze project with keycloak version 20 + cd NETCore.Keycloak.Client.Tests + dotnet cake build_test_analyse.cake --kc_major_version=20 --sonar_token=${SONAR_TOKEN} diff --git a/.gitignore b/.gitignore index 0adf607..497d924 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,8 @@ out **/containers/** **/Assets/*.json +**/NETCore.Keycloak.Client/LICENSE + private.pem location_db.bin @@ -99,6 +101,9 @@ project.lock.json project.fragment.lock.json artifacts/ +# SonarQube +**/.sonarqube + # StyleCop StyleCopReport.xml @@ -173,6 +178,7 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover +**/dotCover* # AxoCover is a Code Coverage Tool .axoCover/* diff --git a/NETCore.Keycloak.Client.Tests/Makefile b/NETCore.Keycloak.Client.Tests/Makefile index 3e05d0b..692ce80 100644 --- a/NETCore.Keycloak.Client.Tests/Makefile +++ b/NETCore.Keycloak.Client.Tests/Makefile @@ -37,7 +37,7 @@ install_virtual_env: fi @echo "Creating directory ${CONF_DIR_CONTEXT}/${VIRTUAL_ENV_DIR}" # Create a new virtual environment - @python3.9 -m venv ${CONF_DIR_CONTEXT}/${VIRTUAL_ENV_DIR} + @python3 -m venv ${CONF_DIR_CONTEXT}/${VIRTUAL_ENV_DIR} # Activate the virtual environment and install dependencies @pushd ${CONF_DIR_CONTEXT}/ && source ${VIRTUAL_ENV_DIR}/bin/activate && pip install -r requirements.txt # Install required Ansible collections diff --git a/NETCore.Keycloak.Client.Tests/ansible.cfg b/NETCore.Keycloak.Client.Tests/ansible.cfg index 0543176..36a3fb9 100644 --- a/NETCore.Keycloak.Client.Tests/ansible.cfg +++ b/NETCore.Keycloak.Client.Tests/ansible.cfg @@ -5,8 +5,6 @@ host_key_checking = False executable = /bin/bash allow_world_readable_tmpfiles = True callbacks_enabled = profile_tasks, profile_roles -stdout_callback = yaml -stderr_callback = yaml forks=50 ssh_args = -o ControlMaster=auto -o ControlPersist=60s pipelining = True diff --git a/NETCore.Keycloak.Client.Tests/build_test_analyse.cake b/NETCore.Keycloak.Client.Tests/build_test_analyse.cake new file mode 100644 index 0000000..a8bac37 --- /dev/null +++ b/NETCore.Keycloak.Client.Tests/build_test_analyse.cake @@ -0,0 +1,107 @@ +/// +/// Main Cake build script to manage the build, restore, test, and setup of the Keycloak testing environment. +/// Includes and calls tasks from external scripts. +/// + +// Load external task scripts +#load "cakeScripts/check_tools.cake"; +#load "cakeScripts/setup_keycloak_test_environment.cake"; +#load "cakeScripts/sonar_analysis.cake"; +#load "../build.cake"; + +// Update the solution context +slnContext = ".."; + +/// +/// Executes unit tests using JetBrains dotCover for code coverage analysis. +/// Runs the tests without rebuilding the project. +/// +Task("Test") + .IsDependentOn("Build") + .Does(() => + { + Information("Running tests with dotCover..."); + + // Ensure dotnet is installed + var dotnetPath = Context.Tools.Resolve("dotnet"); + if (dotnetPath == null) + { + Error("dotnet is not installed or cannot be found."); + Environment.Exit(255); + } + + // Define the test command + var testCommand = $"dotcover test {slnContext}/NETCore.Keycloak.sln --configuration {configuration} -l:\"console;verbosity=normal\" --no-restore --no-build --dcReportType=HTML"; + + // Configure dotCover settings + var processSettings = new ProcessSettings + { + Arguments = new ProcessArgumentBuilder() + .Append(testCommand), + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + // Run tests with dotCover + var result = StartProcess(dotnetPath, processSettings, out var output, out var error); + + // Evaluate the result of the process execution + if (result != 0) + { + Error("Unit tests failed with dotCover. Error:\n{0}", string.Join(Environment.NewLine, error)); + Environment.Exit(255); + } + + Information("Unit tests executed successfully with dotCover."); + }); + + +/// +/// The BuildTestAnalyse task orchestrates the setup, testing, and analysis of the Keycloak client. +/// It ensures that the environment is correctly configured, executes end-to-end tests, +/// and performs SonarQube analysis while validating required parameters. +/// +Task("BuildTestAnalyse") + .Does(() => + { + Information("Executing Keycloak client build, test, and analysis..."); + + // Generate a list of supported Keycloak versions from 20 to 26 + var versions = Enumerable.Range(20, 26 - 20 + 1).ToList(); + + // Get the major version argument if provided + var kcMajorVersion = Argument("kc_major_version", null); + + // Validate the provided version + if (!kcMajorVersion.HasValue || !versions.Contains(kcMajorVersion.Value)) + { + Error($"Invalid Keycloak version: {kcMajorVersion}. Supported versions: {string.Join(", ", versions)}"); + Environment.Exit(255); + } + + // Retrieve the Sonar token from the arguments + var sonarToken = Argument("sonar_token", null); + + if (string.IsNullOrWhiteSpace(sonarToken)) + { + // Ensure the Sonar token is provided before proceeding + Error("Sonar token is required."); + Environment.Exit(255); + } + + // Log the processing of the specific Keycloak version + Information($"Processing Keycloak Version: {kcMajorVersion.Value}"); + + // Set environment variables for Keycloak and SonarQube + Environment.SetEnvironmentVariable("KC_TEST_VERSION", $"prepare_keycloak_{kcMajorVersion.Value}_environment"); + Environment.SetEnvironmentVariable("KC_SONAR_TOKEN", sonarToken); + + // Execute the required setup, testing, and analysis tasks + RunTarget("Setup-Testing-Environment"); + RunTarget("SonarBegin"); + RunTarget("Test"); + RunTarget("SonarEnd"); + }); + +// Execute the BuildTestAnalyse task +RunTarget("BuildTestAnalyse"); diff --git a/NETCore.Keycloak.Client.Tests/cakeScripts/sonar_analysis.cake b/NETCore.Keycloak.Client.Tests/cakeScripts/sonar_analysis.cake new file mode 100644 index 0000000..c550719 --- /dev/null +++ b/NETCore.Keycloak.Client.Tests/cakeScripts/sonar_analysis.cake @@ -0,0 +1,85 @@ +/// +/// Initiates SonarCloud analysis by executing the `dotnet sonarscanner begin` command. +/// This task resolves the `dotnet` executable, ensures the required Sonar token is provided, +/// and configures the necessary SonarCloud parameters for project analysis. +/// +Task("SonarBegin") + .Does(() => + { + // Retrieve the Sonar token from the environment variable + var sonarToken = Environment.GetEnvironmentVariable("KC_SONAR_TOKEN") ?? ""; + + if (string.IsNullOrWhiteSpace(sonarToken)) + { + // Ensure the token is provided before proceeding + Error("Sonar token is required."); + Environment.Exit(255); + } + + // Resolve the path to the `dotnet` executable + FilePath dotnetPath = Context.Tools.Resolve("dotnet"); + + // Configure process settings for executing the SonarScanner command + var processSettings = new ProcessSettings + { + Arguments = new ProcessArgumentBuilder() + .Append("sonarscanner begin") + .Append($"/d:sonar.token={sonarToken}") + .Append("/k:Black-Cockpit_NETCore.Keycloak") + .Append("/o:black-cockpit") + .Append("/d:sonar.host.url=\"https://sonarcloud.io\"") + .Append("/d:sonar.coverage.exclusions=\"**/NETCore.Keycloak.Client.Tests/**/*.*\"") + .Append("/d:sonar.test.exclusions=\"**/NETCore.Keycloak.Client.Tests/**/*.*\"") + .Append("/d:sonar.exclusions=\"**/NETCore.Keycloak.Client.Tests/**/*.*\"") + .Append("/d:sonar.cs.dotcover.reportsPaths=dotCover.Output.html"), + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + // Execute the SonarScanner command + StartProcess(dotnetPath, processSettings); + }); + +/// +/// Finalizes the SonarCloud analysis by executing the `dotnet sonarscanner end` command. +/// This task ensures the required Sonar token is provided, resolves the `dotnet` executable, +/// and captures the process output and error streams for evaluation. +/// +Task("SonarEnd") + .IsDependentOn("Build") + .Does(() => + { + // Retrieve the Sonar token from the environment variable + var sonarToken = Environment.GetEnvironmentVariable("KC_SONAR_TOKEN") ?? ""; + + if (string.IsNullOrWhiteSpace(sonarToken)) + { + // Ensure the token is provided before proceeding + Error("Sonar token is required."); + Environment.Exit(255); + } + + // Resolve the path to the `dotnet` executable + FilePath dotnetPath = Context.Tools.Resolve("dotnet"); + + // Configure process settings for executing the SonarScanner command + var processSettings = new ProcessSettings + { + Arguments = new ProcessArgumentBuilder() + .Append("sonarscanner end") + .Append($"/d:sonar.token={sonarToken}"), + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + // Execute the SonarScanner command and capture output/error streams + var result = StartProcess(dotnetPath, processSettings, out var output, out var error); + + // Evaluate the result of the process execution + if (result != 0) + { + // Log the error message and exit if the command failed + Error("Sonar analysis finalization failed. Error:\n{0}", string.Join(Environment.NewLine, error)); + Environment.Exit(255); + } + }); diff --git a/NETCore.Keycloak.Client.Tests/requirments.txt b/NETCore.Keycloak.Client.Tests/requirements.txt similarity index 100% rename from NETCore.Keycloak.Client.Tests/requirments.txt rename to NETCore.Keycloak.Client.Tests/requirements.txt diff --git a/README.md b/README.md index 5d38c33..62c380a 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,23 @@
-[![GitHub Build Status](https://github.com/Black-Cockpit/NETCore.Keycloak/actions/workflows/build.yml/badge.svg)](https://github.com/Black-Cockpit/NETCore.Keycloak/actions/workflows/build.yml) +[![GitHub Build Status](https://github.com/Black-Cockpit/NETCore.Keycloak/actions/workflows/build_test_analyze.yml/badge.svg)](https://github.com/Black-Cockpit/NETCore.Keycloak/actions/workflows/build.yml) [![NuGet version](https://img.shields.io/nuget/v/Keycloak.NETCore.Client.svg)](https://www.nuget.org/packages/Keycloak.NETCore.Client/) [![NuGet downloads](https://img.shields.io/nuget/dt/Keycloak.NETCore.Client.svg)](https://www.nuget.org/packages/Keycloak.NETCore.Client/) [![GitHub Stars](https://img.shields.io/github/stars/Black-Cockpit/NETCore.Keycloak)](https://github.com/Black-Cockpit/NETCore.Keycloak/stargazers) [![CodeFactor](https://www.codefactor.io/repository/github/black-cockpit/netcore.keycloak/badge)](https://www.codefactor.io/repository/github/black-cockpit/netcore.keycloak) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FBlack-Cockpit%2FNETCore.Keycloak.svg?type=shield&issueType=security)](https://app.fossa.com/projects/git%2Bgithub.com%2FBlack-Cockpit%2FNETCore.Keycloak?ref=badge_shield&issueType=security) +[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=coverage)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) +[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) +[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) +[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) +[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) +[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=bugs)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=Black-Cockpit_NETCore.Keycloak&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=Black-Cockpit_NETCore.Keycloak) [![License](https://img.shields.io/github/license/Black-Cockpit/NETCore.Keycloak)](LICENSE) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FBlack-Cockpit%2FNETCore.Keycloak.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2FBlack-Cockpit%2FNETCore.Keycloak?ref=badge_shield&issueType=license)