From 22ac7ea27a110214dcf065747ba89d63e6585196 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Tue, 13 Aug 2024 13:46:18 +0200 Subject: [PATCH 01/40] feat: Add automated sonarqube and sonarscanner analysis scripts --- script/analysis.sh | 46 ++++++++++ script/cleanup.sh | 10 +++ script/helpers.sh | 77 +++++++++++++++++ script/run_analysis.sh | 36 ++++++++ script/sonarqube_management.sh | 115 +++++++++++++++++++++++++ visualization/sonar-project.properties | 6 +- visualization/sonar.cc.json.gz | Bin 0 -> 2317 bytes 7 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 script/analysis.sh create mode 100644 script/cleanup.sh create mode 100644 script/helpers.sh create mode 100644 script/run_analysis.sh create mode 100644 script/sonarqube_management.sh create mode 100644 visualization/sonar.cc.json.gz diff --git a/script/analysis.sh b/script/analysis.sh new file mode 100644 index 0000000000..a6141d51a8 --- /dev/null +++ b/script/analysis.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Run SonarScanner in the container and capture output +run_sonarscanner() { + echo "๐Ÿ” Running SonarScanner..." + scanner_output=$(docker run --rm --network $NETWORK_NAME \ + -e SONAR_HOST_URL="$CONTAINER_SONAR_URL" \ + -e SONAR_LOGIN="$token" \ + -v "$PROJECT_BASEDIR:/usr/src" \ + sonarsource/sonar-scanner-cli \ + sonar-scanner \ + -Dsonar.projectKey=$PROJECT_KEY \ + -Dsonar.sources=/usr/src 2>&1) + + # Display the output for debugging + echo "$scanner_output" + + if [ $? -ne 0 ]; then + echo "โŒ SonarScanner analysis failed." + exit 1 + fi + echo "โœ… SonarScanner analysis complete." +} + +# Run CodeCharta analysis using docker run +run_codecharta_analysis() { + echo "๐Ÿ“Š Running CodeCharta analysis..." + + # Use the correct hostname 'sonarqube' and execute the analysis + docker run --rm -it --network "$NETWORK_NAME" --name codecharta-analysis \ + -v "$PROJECT_BASEDIR:$PROJECT_BASEDIR" \ + -w "$PROJECT_BASEDIR" \ + codecharta/codecharta-analysis \ + ccsh sonarimport "$CONTAINER_SONAR_URL" "$PROJECT_KEY" "--user-token=$token" "--output-file=./sonar.cc.json" "--merge-modules=false" + + if [ $? -ne 0 ]; then + echo "โŒ CodeCharta analysis failed." + exit 1 + fi + + echo "โœ… CodeCharta analysis complete. Output stored in $OUTPUT_PATH" + + # List the contents of the output directory for verification + echo "Contents of $OUTPUT_PATH:" + ls -l "$OUTPUT_PATH" +} diff --git a/script/cleanup.sh b/script/cleanup.sh new file mode 100644 index 0000000000..2f1f339f89 --- /dev/null +++ b/script/cleanup.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Cleanup function: Stop and remove containers and network +cleanup() { + echo "๐Ÿงน Cleaning up..." + docker stop $SONAR_CONTAINER_NAME 2>/dev/null + docker rm $SONAR_CONTAINER_NAME 2>/dev/null + docker network rm $NETWORK_NAME 2>/dev/null + echo "๐Ÿงน Cleanup complete." +} diff --git a/script/helpers.sh b/script/helpers.sh new file mode 100644 index 0000000000..c588ed7734 --- /dev/null +++ b/script/helpers.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Function to check the last operation result +check_response() { + local http_status=$1 + local response=$2 + local error_message=$3 + + if [[ "$http_status" -ne 200 && "$http_status" -ne 204 ]]; then + case "$http_status" in + 400) + echo "โŒ Failed: $error_message (400 Bad Request)" + ;; + 401) + echo "โŒ Failed: $error_message (401 Unauthorized)" + ;; + 403) + echo "โŒ Failed: $error_message (403 Forbidden)" + ;; + 404) + echo "โŒ Failed: $error_message (404 Not Found)" + ;; + 500) + echo "โŒ Failed: $error_message (500 Internal Server Error)" + ;; + *) + echo "โŒ Failed: $error_message (HTTP status $http_status)" + ;; + esac + + echo "Response:" + echo "$response" | jq '.' + exit 1 + fi +} + +# Function to change the default password if necessary +change_default_password() { + echo "๐Ÿ”‘ Checking if the default password needs to be changed..." + + # Attempt to authenticate with the default password + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s "$HOST_SONAR_URL/api/authentication/validate") + is_valid=$(echo "$response" | jq -r '.valid') + + if [ "$is_valid" == "false" ]; then + echo "๐Ÿ”„ Changing default password..." + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X POST -s \ + -d "login=$DEFAULT_SONAR_USER&previousPassword=$DEFAULT_SONAR_PASSWORD&password=$NEW_SONAR_PASSWORD" \ + "$HOST_SONAR_URL/api/users/change_password") + + if [ "$(echo "$response" | jq -r '.errors')" == "null" ]; then + echo "โœ… Password changed successfully." + else + echo "โŒ Failed to change password. Response: $response" + exit 1 + fi + else + echo "โ„น๏ธ Default password does not need to be changed or has already been changed." + fi +} + +# Function to generate a token for SonarScanner +generate_token() { + echo "๐Ÿ”‘ Generating token..." + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/generate?name=$TOKEN_NAME") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n-1) + check_response $http_status "$response_body" "Token generation failed." + token=$(echo "$response_body" | jq -r '.token') + if [[ -z "$token" || "$token" == "null" ]]; then + echo "โŒ Failed to generate token." + exit 1 + fi + echo "โœ… Token generated: $token" + echo "Token response:" + echo "$response_body" | jq '.' +} diff --git a/script/run_analysis.sh b/script/run_analysis.sh new file mode 100644 index 0000000000..77148ce59e --- /dev/null +++ b/script/run_analysis.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Import the necessary helper scripts +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi + +. "$DIR/helpers.sh" +. "$DIR/cleanup.sh" +. "$DIR/sonarqube_management.sh" +. "$DIR/analysis.sh" + +# Define variables +HOST_SONAR_URL="http://localhost:9000" # Host's URL to access SonarQube +CONTAINER_SONAR_URL="http://sonarqube:9000" # Container's URL to access SonarQube +PROJECT_KEY="test_key" +PROJECT_NAME="test_project" +DEFAULT_SONAR_USER="admin" +DEFAULT_SONAR_PASSWORD="admin" +NEW_SONAR_PASSWORD="newadminpassword" # Define the new password you want to set +TOKEN_NAME="codecharta_token" +PROJECT_BASEDIR="$(pwd)/visualization" # Directory to be scanned +OUTPUT_PATH="$(pwd)/output" # Directory for the output +NETWORK_NAME="sonarnet" +SONAR_CONTAINER_NAME="sonarqube" + +# Run the steps +ensure_sonarqube_running +reset_sonarqube_password +cleanup_previous_project_and_token +create_sonarqube_project +generate_token +run_sonarscanner +run_codecharta_analysis + +# Final cleanup +cleanup diff --git a/script/sonarqube_management.sh b/script/sonarqube_management.sh new file mode 100644 index 0000000000..2c9facc47d --- /dev/null +++ b/script/sonarqube_management.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +# Ensure the Docker network exists +ensure_network_exists() { + if ! docker network inspect $NETWORK_NAME >/dev/null 2>&1; then + echo "๐Ÿ”ง Creating Docker network $NETWORK_NAME..." + docker network create $NETWORK_NAME + if [ $? -ne 0 ]; then + echo "โŒ Failed to create Docker network $NETWORK_NAME." + exit 1 + fi + else + echo "โ„น๏ธ Docker network $NETWORK_NAME already exists." + fi +} + +# Step 1: Ensure the SonarQube container is running +ensure_sonarqube_running() { + # Ensure the Docker network exists before running the container + ensure_network_exists + + # Check if the SonarQube container is already running with the correct settings + existing_container=$(docker ps -a -q -f name=$SONAR_CONTAINER_NAME) + if [ "$existing_container" ]; then + running_container=$(docker ps -q -f name=$SONAR_CONTAINER_NAME) + if [ "$running_container" ]; then + echo "โ„น๏ธ SonarQube container is already running." + return + else + echo "โš ๏ธ SonarQube container exists but is not running. Starting it..." + docker start $SONAR_CONTAINER_NAME + if [ $? -ne 0 ]; then + echo "โŒ Failed to start existing SonarQube container." + exit 1 + fi + return + fi + fi + + # If no container exists, create and start a new one + echo "๐Ÿš€ Starting SonarQube container..." + docker run -d --name $SONAR_CONTAINER_NAME --network $NETWORK_NAME -p 9000:9000 sonarqube:community + + # Wait for SonarQube to be ready only after a new container is created + echo "โณ Waiting for SonarQube to be ready..." + sleep 60 # Adjust this sleep time as needed to allow SonarQube to fully start +} +# Step 2: Reset the password if needed +reset_sonarqube_password() { + # Attempt to log in with the default password to check if it's still active + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s "$HOST_SONAR_URL/api/authentication/validate") + is_valid=$(echo "$response" | jq -r '.valid') + + if [ "$is_valid" == "false" ]; then + # The default password is still active, so we need to change it + echo "๐Ÿ”„ Changing default SonarQube password..." + change_default_password + SONAR_USER=$DEFAULT_SONAR_USER + SONAR_PASSWORD=$NEW_SONAR_PASSWORD + else + echo "โ„น๏ธ Default password is not active, using existing credentials." + SONAR_USER=$DEFAULT_SONAR_USER + SONAR_PASSWORD=$DEFAULT_SONAR_PASSWORD + fi +} + +# Cleanup previous SonarQube project and token +cleanup_previous_project_and_token() { + echo "๐Ÿงน Cleaning up previous SonarQube project and token..." + + # Delete project + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/delete?project=$PROJECT_KEY") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n-1) + if [ "$http_status" -eq 404 ]; then + echo "โ„น๏ธ Project not found, skipping deletion." + elif [ -z "$http_status" ]; then + echo "โŒ Failed to connect to SonarQube. Ensure that SonarQube is running and accessible at $HOST_SONAR_URL." + exit 1 + else + check_response $http_status "$response_body" "Project deletion failed." + echo "โœ… Project deleted successfully." + fi + + # Revoke token + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/revoke?name=$TOKEN_NAME") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n-1) + if [ "$http_status" -eq 404 ]; then + echo "โ„น๏ธ Token not found, skipping revocation." + elif [ -z "$http_status" ]; then + echo "โŒ Failed to connect to SonarQube. Ensure that SonarQube is running and accessible at $HOST_SONAR_URL." + exit 1 + else + check_response $http_status "$response_body" "Token revocation failed." + echo "โœ… Token revoked successfully." + fi +} + +# Create SonarQube project +create_sonarqube_project() { + echo "๐Ÿš€ Creating project in SonarQube..." + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/create?project=$PROJECT_KEY&name=$PROJECT_NAME") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n-1) + if [[ "$http_status" -eq 404 ]]; then + echo "โŒ Failed: Project creation failed. The endpoint may be incorrect or deprecated." + echo "Response: $response_body" + exit 1 + fi + check_response $http_status "$response_body" "Project creation failed." + echo "โœ… Project created successfully." + echo "Project creation response:" + echo "$response_body" | jq '.' +} diff --git a/visualization/sonar-project.properties b/visualization/sonar-project.properties index d0f8962ea2..8e5b751488 100644 --- a/visualization/sonar-project.properties +++ b/visualization/sonar-project.properties @@ -1,4 +1,4 @@ -sonar.projectKey=maibornwolff-gmbh_codecharta_visualization +# sonar.projectKey=maibornwolff-gmbh_codecharta_visualization sonar.projectName=CodeCharta Visualization sonar.sources=./app sonar.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts,**/*.spec.js,**/*.e2e.ts,**/*.e2e.js,**/*.po.ts,**/app/codeCharta/util/testUtils/* @@ -7,8 +7,8 @@ sonar.test.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts sonar.test.inclusions=**/*.spec.ts sonar.coverage.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts,**/*.spec.js,**/*.e2e.ts,**/*.e2e.js,**/*.po.ts,**/*.html,**/*.scss,**/app/codeCharta/util/dataMocks.ts,**/app/codeCharta/util/testUtils/* sonar.cpd.exclusions=**/app/codeCharta/util/dataMocks.ts -sonar.host.url=https://sonarcloud.io -sonar.organization=maibornwolff-gmbh +# sonar.host.url=https://sonarcloud.io +# sonar.organization=maibornwolff-gmbh sonar.links.ci=https://travis-ci.org/MaibornWolff/codecharta sonar.links.issue=https://github.com/MaibornWolff/codecharta/issues # Disabling rules diff --git a/visualization/sonar.cc.json.gz b/visualization/sonar.cc.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..5a17d871d727438bd3f4d7994f05138565d05796 GIT binary patch literal 2317 zcmV+o3G((IiwFP!000000Oed?Z`(K$|0;x^xj})X~YSuanH^KR9zA=!#EX zO(st#N+;Y-Uj6=cqC%U|+CjU!%kbBwmI*iTn>uI2Ja@?a_1n{l6zV4Qz)~R-!xeP$ z<=YqF%*1DItkBRhIeYd3Sa`DZgM6v=^9cqh`r5W#aLbIyoHj_ASgoj;QO0=Ycrp{# z=Khh16Hemnel1AkZ6H0JBt;Kl^etVK^i(MKPH#R_nHK`UmN#OV)|v`zsdJfqb$Xg; zW}o@FFsbw8nb4<+ST2QHo+O-S+?=jB0ATQIA(X(e?J3Sh7!b9e$;lbO3~=UBvm0(^ zYoR6eymdsc1C@NPs7+9xw4;(RohwK5qJW0qT*`^%Yi@+QBgHUrK+cytAV1`*Ibeh- zzeY+EU`2)pV_G^UskJkvSDECuAmlox=^c}Tktwo}X~oh6_nzL0RlXt&`1J~wn31YO zGN%@_RV$KFms(g+qw{4-DDgv+`5k%BrgYBZ5eM&C*=a%SU)m^8_YA6t`%t z1?4FLK-?@S!|z<>D@&+K;G@6G(8mqp_bkVrXRbV5-{ z;F&oMzaZw^^$#T(t$gaq193pby-5X2aa)uLnWJsMlOPxXpDWxvAUud=Yg7wiR-H-7 zw50WACEWo$_{QK5%BNwAbdfAKWE6r?*^{$O3-aTWmU5sdP$zi?Zk%G~>657)%okfC zc^!!T3SM;-vNF335Msl*5R&)7y!5nwl>S-J;xpqf5RGD`$=Trb1*_Y+ z+5EdCjqWwuPNGVan*bGb$GQQ2sflR6kgx zeFw1E&4Iz1hTYz4*J+rpOQ|a>_XNI5PA%Yv42h-GfHQ`tg0c%s)V8hY0B_fJ26yX zw7r1UM*2b+P#h;#ye#g)l!CJaEJ7#oh^hC0-iv7XkRCxrc2JL)dQN2#&|^+!k9Uzr ziuOS;_bJ>%r>A+A_;~#oi}+qB!=-I6ls(GZqb{Nx=Xlj80Vq;u{$!YG>zZP^sOX?M z)^*eoD+cVKtwNFHcOLCf@!!M!E(ZX(=*J*~Rt2LWg2$E2k4g$%rSvXoJfxl;wIBx4 zf5bml0vL6X*#Ggm$F_y+#86`fQeXA>DqdTFy^+J-MT1_T#}zjW6%kM#qlJV~bMPY^ zuRRPE9=w3YiVx!g@;w}_fGlS@;+Un{*$h5S30)TqcXeR1-u6!%`_3CX4M?r!I2whVl>0Cy#E&)ldpxZqB%cTd_Z#9-q zj5`eRAvGLLqWk!4t6v`rrU#i^IYLBcH<-QLCv#~9n{mP$N=<%bS`w)=hVqXem3zb? ziW`w-par2C)oJPl3;9$Ota_mqe34bRhSI3NCWSh*7|_Y$Zqu{A*A;zWaqCQnUsP1D z88@bQu|gXZDl7R{VCY9xscN_-lhQSW{;(PIIO2N`6X%ngzKz%ey)0QC1U1~wqzZWW zZ3feTCVq<-V6c-I@!f43YzGPJE+{*ifn6!3cT_3fPReSr_JwMiLVR3s1wa`Fo2 z-kEuXS^3;h#nNzgMwfi&l`ozY4$hD?a9+H~rM#<7#7nfriR+;&#Cu7HUYOvE!)d+jdbX(Iyh5%s3L=@A~C-}zK9t92TK zB>F2s`;~y&6ybf!?eByETHM zH}DEYw&maOCFP+jBDL1B`&VK&q n%a_m3U(oZjUwE=OJ3l{rPU(yD#fx9~ Date: Tue, 13 Aug 2024 16:39:52 +0200 Subject: [PATCH 02/40] chore: Update SonarQube and SonarScanner analysis script --- script/analysis.sh | 14 +- script/helpers.sh | 67 ++- script/run_analysis.sh | 35 +- script/sonarqube_management.sh | 23 +- visualization/sonar-project.properties | 6 +- visualization/sonar.cc.json.gz | Bin 2317 -> 74900 bytes visualization/sonar.cc.json/sonar.cc.json | 685 ++++++++++++++++++++++ 7 files changed, 801 insertions(+), 29 deletions(-) create mode 100644 visualization/sonar.cc.json/sonar.cc.json diff --git a/script/analysis.sh b/script/analysis.sh index a6141d51a8..278d80f14f 100644 --- a/script/analysis.sh +++ b/script/analysis.sh @@ -3,17 +3,15 @@ # Run SonarScanner in the container and capture output run_sonarscanner() { echo "๐Ÿ” Running SonarScanner..." - scanner_output=$(docker run --rm --network $NETWORK_NAME \ - -e SONAR_HOST_URL="$CONTAINER_SONAR_URL" \ - -e SONAR_LOGIN="$token" \ + docker run --rm \ + --network $NETWORK_NAME \ -v "$PROJECT_BASEDIR:/usr/src" \ sonarsource/sonar-scanner-cli \ sonar-scanner \ -Dsonar.projectKey=$PROJECT_KEY \ - -Dsonar.sources=/usr/src 2>&1) - - # Display the output for debugging - echo "$scanner_output" + -Dsonar.sources=/usr/src \ + -Dsonar.token=$token \ + -Dsonar.host.url="$CONTAINER_SONAR_URL" if [ $? -ne 0 ]; then echo "โŒ SonarScanner analysis failed." @@ -31,7 +29,7 @@ run_codecharta_analysis() { -v "$PROJECT_BASEDIR:$PROJECT_BASEDIR" \ -w "$PROJECT_BASEDIR" \ codecharta/codecharta-analysis \ - ccsh sonarimport "$CONTAINER_SONAR_URL" "$PROJECT_KEY" "--user-token=$token" "--output-file=./sonar.cc.json" "--merge-modules=false" + ccsh sonarimport "$CONTAINER_SONAR_URL" "$PROJECT_KEY" "--user-token=$token" "--output-file=sonar.cc.json" "--merge-modules=false" if [ $? -ne 0 ]; then echo "โŒ CodeCharta analysis failed." diff --git a/script/helpers.sh b/script/helpers.sh index c588ed7734..20132c0079 100644 --- a/script/helpers.sh +++ b/script/helpers.sh @@ -39,9 +39,9 @@ change_default_password() { echo "๐Ÿ”‘ Checking if the default password needs to be changed..." # Attempt to authenticate with the default password - response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s "$HOST_SONAR_URL/api/authentication/validate") + response=$(curl -X GET -s "$HOST_SONAR_URL/api/authentication/validate") is_valid=$(echo "$response" | jq -r '.valid') - + echo "Response: $response" if [ "$is_valid" == "false" ]; then echo "๐Ÿ”„ Changing default password..." response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X POST -s \ @@ -59,19 +59,64 @@ change_default_password() { fi } -# Function to generate a token for SonarScanner +# Function to check if a token is valid +is_valid_token() { + local token=$1 + if [[ "$token" =~ ^squ_[0-9a-f]{40}$ ]]; then + return 0 # Valid token + else + return 1 # Invalid token + fi +} + +# Function to check if a token exists (but note: SonarQube does not return the token value once created) +token_exists() { + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X GET -s "$HOST_SONAR_URL/api/user_tokens/search") + + # Directly parse the response to check if the token exists + existing_token_name=$(echo "$response" | jq -r ".userTokens[] | select(.name == \"$SONARQUBE_TOKEN_NAME\") | .name") + + if [[ "$existing_token_name" == "$SONARQUBE_TOKEN_NAME" ]]; then + return 0 # Token exists + else + return 1 # Token does not exist + fi +} + +# Function to generate a token for SonarScanner only if it doesn't already exist generate_token() { + if [[ -n "$SONARQUBE_TOKEN" ]]; then + echo "โ„น๏ธ Using predefined token." + token=$SONARQUBE_TOKEN + if ! is_valid_token "$token"; then + echo "โŒ Predefined token is invalid." + exit 1 + fi + return + fi + + echo "๐Ÿ” Checking if token '$SONARQUBE_TOKEN_NAME' already exists..." + + # Check if the token exists + if token_exists; then + echo "โŒ Token '$SONARQUBE_TOKEN_NAME' already exists. Cannot retrieve the value after generation." + exit 1 + fi + + # If the token does not exist, generate a new one echo "๐Ÿ”‘ Generating token..." - response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/generate?name=$TOKEN_NAME") - http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n-1) - check_response $http_status "$response_body" "Token generation failed." - token=$(echo "$response_body" | jq -r '.token') - if [[ -z "$token" || "$token" == "null" ]]; then - echo "โŒ Failed to generate token." + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s "$HOST_SONAR_URL/api/user_tokens/generate?name=$SONARQUBE_TOKEN_NAME") + + # Extract the newly generated token from the response + token=$(echo "$response" | jq -r '.token') + + # Since this is a new token, ensure it follows the correct format + if ! is_valid_token "$token"; then + echo "โŒ Failed to generate a valid token." exit 1 fi + echo "โœ… Token generated: $token" echo "Token response:" - echo "$response_body" | jq '.' + echo "$response" | jq '.' } diff --git a/script/run_analysis.sh b/script/run_analysis.sh index 77148ce59e..0a774dbf67 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -17,20 +17,45 @@ PROJECT_NAME="test_project" DEFAULT_SONAR_USER="admin" DEFAULT_SONAR_PASSWORD="admin" NEW_SONAR_PASSWORD="newadminpassword" # Define the new password you want to set -TOKEN_NAME="codecharta_token" +SONARQUBE_TOKEN_NAME="codecharta_token" # Name of the SonarQube token +SONARQUBE_TOKEN="" # Token generated for SonarScanner (optional, will generate a new one if empty) PROJECT_BASEDIR="$(pwd)/visualization" # Directory to be scanned OUTPUT_PATH="$(pwd)/output" # Directory for the output NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" +RUN_CLEANUP=true # Set to false to skip cleanup +RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner +WAIT_TIME=60 # Time in seconds to wait after running SonarScanner + # Run the steps ensure_sonarqube_running + +# Conditionally reset password reset_sonarqube_password -cleanup_previous_project_and_token + +# Conditionally clean up previous project and token +if $RUN_CLEANUP; then + cleanup_previous_project_and_token +fi + +# Create the project and generate the token create_sonarqube_project generate_token -run_sonarscanner + +# Conditionally run the SonarScanner +if $RUN_SONAR_SCANNER; then + run_sonarscanner + + # Wait for the data to be fully uploaded to SonarQube + echo "โณ Waiting for $WAIT_TIME seconds to ensure data is uploaded to SonarQube..." + sleep $WAIT_TIME +fi + +# Run the CodeCharta analysis run_codecharta_analysis -# Final cleanup -cleanup +# Final cleanup if enabled +if $RUN_CLEANUP; then + cleanup +fi diff --git a/script/sonarqube_management.sh b/script/sonarqube_management.sh index 2c9facc47d..53e940d413 100644 --- a/script/sonarqube_management.sh +++ b/script/sonarqube_management.sh @@ -83,7 +83,7 @@ cleanup_previous_project_and_token() { fi # Revoke token - response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/revoke?name=$TOKEN_NAME") + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/revoke?name=$SONARQUBE_TOKEN_NAME") http_status=$(echo "$response" | tail -n1) response_body=$(echo "$response" | head -n-1) if [ "$http_status" -eq 404 ]; then @@ -97,17 +97,36 @@ cleanup_previous_project_and_token() { fi } -# Create SonarQube project +# Create SonarQube project only if it doesn't already exist create_sonarqube_project() { + echo "๐Ÿ” Checking if project '$PROJECT_KEY' already exists in SonarQube..." + + # Check if the project already exists + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X GET -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/search?projects=$PROJECT_KEY") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n-1) + + if [[ "$http_status" -eq 200 && $(echo "$response_body" | jq -r '.components | length') -gt 0 ]]; then + echo "โ„น๏ธ Project '$PROJECT_KEY' already exists. Skipping creation." + return + elif [[ "$http_status" -eq 404 ]]; then + echo "โŒ Failed: Unable to check if the project exists. The endpoint may be incorrect or deprecated." + echo "Response: $response_body" + exit 1 + fi + + # If the project does not exist, create it echo "๐Ÿš€ Creating project in SonarQube..." response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/create?project=$PROJECT_KEY&name=$PROJECT_NAME") http_status=$(echo "$response" | tail -n1) response_body=$(echo "$response" | head -n-1) + if [[ "$http_status" -eq 404 ]]; then echo "โŒ Failed: Project creation failed. The endpoint may be incorrect or deprecated." echo "Response: $response_body" exit 1 fi + check_response $http_status "$response_body" "Project creation failed." echo "โœ… Project created successfully." echo "Project creation response:" diff --git a/visualization/sonar-project.properties b/visualization/sonar-project.properties index 8e5b751488..d0f8962ea2 100644 --- a/visualization/sonar-project.properties +++ b/visualization/sonar-project.properties @@ -1,4 +1,4 @@ -# sonar.projectKey=maibornwolff-gmbh_codecharta_visualization +sonar.projectKey=maibornwolff-gmbh_codecharta_visualization sonar.projectName=CodeCharta Visualization sonar.sources=./app sonar.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts,**/*.spec.js,**/*.e2e.ts,**/*.e2e.js,**/*.po.ts,**/app/codeCharta/util/testUtils/* @@ -7,8 +7,8 @@ sonar.test.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts sonar.test.inclusions=**/*.spec.ts sonar.coverage.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts,**/*.spec.js,**/*.e2e.ts,**/*.e2e.js,**/*.po.ts,**/*.html,**/*.scss,**/app/codeCharta/util/dataMocks.ts,**/app/codeCharta/util/testUtils/* sonar.cpd.exclusions=**/app/codeCharta/util/dataMocks.ts -# sonar.host.url=https://sonarcloud.io -# sonar.organization=maibornwolff-gmbh +sonar.host.url=https://sonarcloud.io +sonar.organization=maibornwolff-gmbh sonar.links.ci=https://travis-ci.org/MaibornWolff/codecharta sonar.links.issue=https://github.com/MaibornWolff/codecharta/issues # Disabling rules diff --git a/visualization/sonar.cc.json.gz b/visualization/sonar.cc.json.gz index 5a17d871d727438bd3f4d7994f05138565d05796..87bde899d2c8b7d1119867e4a2b9c3ea2393ed24 100644 GIT binary patch literal 74900 zcmXuKb8sf@^F5q9wr$(CZETX=*tTukww-M3JKBwH+qRS6KHvBAPFGD`)qhOY^fl9c z&Y3er(a<3O-*M5So4hXBTKB5&)00gfK=kJ<8%;yT&U>KIm7d^Pd_taEnf%8e2F!kh zphg$BoNqx8U;~H=BY6T0YNn5UCj)9GxynJ4M(_DK(Elg6%*~+C_j|Yk{--&&k{_Jj zAko+BAkz2kAg@-)`oHz}$1~FTps^!5#p(X&v%*t-e$gUn%)CF83@TH;n~dA7|9AfP zi(!k7KhFiT|F1es$M&-9l5`4xUl<_X`boZh@oPHsSGP{6$@Cx)mhY<}c1h%8`Dxs# z2?opG^n5=28q)pS(d!5pr2eu@>LdFV6>Nfok+%WV!5&uWt`+>^`B~@YNbOE%uA~_ZREe^jn~s3biLE`(w43N;X&z7 z<0O&L8?BJ!cXsI3MOW*mb!lI4G0A_KH(uWg{k_&3R;~Ved+hq=2Mk+%U#%IiKjdPG zU&>Fvt$xup@drMj#On~2cTEPTUc@rEWvjpWJXJmsiM(AnTHhuwcKz~G^$-(+sK}9M-y=TihDPxRsCKvt+`Bnv{ovEN$^J+cAEx_WqZDbR zyTY{{XgM*q-!o0|W%+XV(%jpW^*HD1Dt}=5SHZ07#%&2N(}rKNenG?5?&o`pp`Uxq z-duY^!5{PaLh4?wicRU{s~^tX8%$sIn`$r9;ay!_hELgVMB{(H|1I$thD^xdKfb@F z{c~8bZ8+Dc6`rxXC8Hng5a)ol2;%cd7-r9w-XB>6c7t{T@x5V=U$#`!zvOhrfsqWo zA9+4IiQG$xsNW&LrwwCu#|ddR(B3Ky z@7LD4-@|)m<@DP=!dHX@$owk?rR0IL?{IMbgGZ8$1Z!#sh~q1%Xnqa3_SXIIOIys= z?*KV`5HHZd*%$a;mX6}|vUL<~)CN;)BK;t4m}Y?$pK6gewy6r-x~$j62PjKAF&yC> zpW3db`a^iZ2)3n(>mqG14qFwrQ5CjDr;;78g*T8(%N#H#YI9SDa;Vj5Dy{NotDB_m zx{W7k3HHt8E7Xl zl^16@&D@(iF7skVQT$d>^AA!umwO#3z&Gvom*L}2qrt9EvJXs?Z8n4L=B_Ev^QnxG z*^ueq*a{iSIc9PAHW9t`h=We9gHD`igP#cr>LX@jS51y7g$$}8~OhkK!yp?v}J;m@O)|E(9!sTh3QLV&lW|hj~ zVZ9{F(MKNLNoUDG;d6SL!P3Ve#ytAf8=J^5b2(OYtsp~zQHrr;wFU&PQ!3~dg@E(b zPi7D^gn(!WWJq`rSmGG$0KO#<5BYuSTF{8|TOi{8+=HsZMc2-{m{OpNlcRVdCLQlk zps`b?Pcf!)TBABV*=!V5%9-MIPd_K&Y?4$8SMX$r7QqPid=LG(LQs}g}&hf zF6FIzFiye!=U;op+5I^>b_LMGm(vcA8Pq;W`o1%c%aozybuw&mTe8nc$r=nIN1C{k zAKSH>o&Y*B7HBbwf^nxn@oZ88>4UPZTpIm?axyzBw!Hb;{`*$`Y3&oCfGNBA4u}*X zKi{Q&)=R^Yr%l z4XliSr7iw^H*Y0BdyFw_&Wxb%ZFe-{@D6s68^Mj#(xOCV50`$`8^>4a6>sPJ|(KgO1oAi1){A@fFUJU0$yCsZJ+I(VPY& z%sOkFq?vIKH=a#9S2PbLwotd|WM1DHU9ShxkMG-dIX`*q8i5bl7i~+RU*w*JvftT*LY9R`@LyC9du}h~ zZ4fW^(ch~C(ce&#?N9yw>96%5E|DxAi@(8MRM{;#cOA>-oJmNYsapgaGO`>S#h;rv zowaovtQ!`~my5OgG@k3XLpBf>e(NlP+hjxf8A9R4j*|~RoFzu`TXqm++}=Ha%}1^+ z(?{BxK*5S=NnHpR*8I*TC}@Ll>$Bn`2JIZe{?Po$uminv@B2hqEg184r$mAp_xWt+)5gkV;v#Ch^# zcd-dQmzHt4(x^&_!lmk@TkTJVP=msV{%_KaN?f~t87b&O6Ob^{0B5!FMmk4PW&)BO z)k=P>pwr=@KQwT-`J=|S>|mL(DTP6+PU1MM2|aT5aPp4z(@>@p=_`{cy(%qCHr`}Y zXof{&p8wh<6(NMZ^U%0B*`A6}Xt2O|oKC3O!6mnnkRZ1`(J{uk@9Bf= zkZ)JAcL|x=E+Yi6ooC|KRfVrG3E(>iLKLv? zlXBx&%dmY0X20Sz=GWPKj7N>SeteJ(JPz1RDlAadOXBMEET{zuU@G<2p**TD;CjXT~TGo_2~7As;r)1nbU`!ChKM0e9NQzierO zXl0~wJipf?jP{FAIF+N|$ZX=x)OK5H&P5w6CtdbX0!}H)=HZj9(qco3AKJ-4-a4S0 zr3NsvwRsNFF3b-uG)Mai>kCt~Wt4DS8x#D^di1BgM5hE#MB)8=Q11~=BzrS@4Bjv@ z^bA@;jw2Q2F$S9OPd4zq?{b{*2Z&<4L+_2>;LpwPL60MDw?_%)9%dle-O1E?Z?+O? zuDQq2T4qj<8d%qjIv`iNK;A>n!+BIHg3gku2TR9etk+t%*-90Y-&D&fUCPNd%e(4T zq1RQ)@jrt!;{+*HsZe%ngZ-Za(s#ASp67>Of!p6zLMXoJf}CET-EGVW#SJx}3OQ|h z{fN$}OafH5R})59)@krVl`6kYNacNzzYh$p>cEf%ET7C|N&7#P11wj;b-WIZpB{bf z!d&Cd7(*}su1(7BzcG3MzaVN)Vh;#!m5*_4Eg-&`rd(vta2x&Q{j0`bF@q6B9c(3celRI((H@p$GX$%TmAd@dX?Ldkowvc-$JmvVXTiPCmAnv~ z7%ov(ukZMC)!i@W`^YEXR=rQk(znk4Y8SCe3kz^aWnRrj%O3EX9#eY)#MWFNmJ0;3 zjA`~KPg$vnKBJ(7J*#$uj*xZrg9{r28z4U;@|0C$AQvW_P=8X4EH3pymS9^>Aji7_ zyd}(l7khK-73`=`=qOOslbWh|L)6BffA?+Q38fci&)0I17JRpBtR9FkaKAahZ|1g3 zA+@46yLcUQ1No*IbR$68jax|J^+^khDP?u;`MsKdErwiP4-;*$M(U7D{+AJ45|MK^ zu)gS#zwCN-#b%!^aogX$71ocWTEzl$1S2gY6sM=%bO%s7{br1%XhM`WN`J1BqxU3* zPSMwP`nwsvXVf`uP@7E0j1x@%VCMKla$1(818|iW#7E^63x*_6R!zvM<6DD<0nU;s zNDJ|jf`?U_nzf;vMDtP^hrA)|C)mS4?4bnB7}oT|W}5Mv;++wKjx0;wQt05yWii!n zDAe>5WtP%pmCv`4z_!W#FIlhs=S!Z-Bb>WTEt6uh+o9A_(MyJCam@9|3@Ru{tCz4|ERr#p$cLw)M^UtFCpe%VfKO81b9GyE1GNYlLfV z@(DQEd`!oAd*<9*5Pqd6Ii~yyJrQxvP_P<;OL!=uy+gz>CY7%}7e7h*Q(oO8Z; zX{U0s$Ty+*D5(&1MZ2&kwBvG`XE(%&BwY@jY2m8Z(-!+ThEy zWrGq}Lcj5K_u5ANO15#OG5VDL*tM*1_vzcvv2YHLm|wkX7|K`8Eo_JFgf>aF5hsSH zTu3a6bBZ=hWh|c|Z{e0;oEvQVx83vy0io@Pe~9H_e_QhqHZxXk#a z`8ekMO9?HV2V4eKez0fgl(CI-{B11ht_d97fPAtTyD;NU4hI{8IGSIFUNU2CwJa?m zHjuU8T9L%Zg5e$Yl`Eojff_~#6)@Q0{1m2oO&%y4W)JsV`mZjFeqRcxM+^WS)FY91 zN>~_OP+#!32&b3ZqnK;D-t-)$fY| zcOmNcD;tuCfc=Vv8_=iO7=z}1tURc`G-5+z5o8n;u3sQq>ke`ip{S4(va{JWCF z4$bG81{^AzWc${9?klKn5Zl|!h{FpL;qV=F%S?eBq2<}tGzMu0o zp?7;*$b3xgWm=BMcg(ZbEnT&H#X9=WNADwp)wG0mMd$uHqX zi`|2}EC|~}gxP@_i@?S;jGjGGOHu@ZANP4>A)l`kUN|?x1#CwfqaO*;g5|MVut>bRjr?rJSA!PXVHqoyC zW!kON=ZBPs2L{BuqnLw@IF&g%lm~PRVbISMOE?E=4qgyV`s{Wpv$+bi`9J^)vi}I0 zwwq1?o(R&}QAA%VMTc4{yPtu-9-3LJxqEGIl^1ei=|$_n&=9(y8XuZiTR`M#KaNO< z2TyHgQ$MFXp?;BVyB|>*eEzvRTZJ9W;B4$JF$E9CpomdvD<0w&6CT>RGADjIJT?}r z5CIE*C?>)sJX8+@Gky;HO9@+?^ z+09F{4-v*BQ9s!`&9Rhn_rhXAE}o;FH%9X<12ddsxa2Rbr*@HSHf>@3Lyq}z8{qh` zuMV$Hw>E!xc+b}-YMsB#mgKHmZufiIS~St}wtg`;I~ivskT8XM0dJwC*2|o1&t3Dc zc51xNMVO2r8m_*U70SZu+JjdM$RExZT)`KaX7OaXMU1cII}@c(1j9r%1E<=`=JZe z)J-~mATY`L#RiGEDf&y%7j;s{1(QMVPYFQo z-UB{pg(TuD0e*^l*`)%=^XCt<@cS-)!3zfW+D4e-?^#8PiKPdeVOo9K{J>c)bRKF>Bu+&pHAYo10=DI7kmgNcbU^F*W2f5la*S0}!g6La| z#)1&_Soqek6-tg^PxCL0Tmo84R&Z}ua2+9J57JjCJxCqwN4C+fCM9O|N?U5;Es*_T zu!vN}klFY6d{!kHigkt7b(Sp$`T^Xd-`o9_nq*tMi!1Vpm$B&WxB z_m&$h1g>%C>I&iOqlMTm*Hx$7O&uTClpRI>jBVrP>Mt~FAGrLXW?_pTt=_({``beK zT%n81HXI`&KqHO~OMuF9OX4ZWtnaG5-wt(*i{p?LHn+n+@u+$huUZB2OBvDTS6PJv z5k4~S+4EpQ9=|!ynH2w-)Py*zf*~FhI6kr+#3Ss18G6~JpG_c)ElrPD*YYN9SGs*L zIdjkU&VwSJWFrs5UuTgR%Y!9ewi+x%soFt9H(`n+9K2HrUNhYy8m^`SCK*}tWEOgo z&W~%v#>l7Tn8dEHZI!FcqtY zPm&>dH~cKl{ua_e-1`e#4gqla_K-;y6*f~2IN!bEF&1Io09}v}oy({5X{fEf>o8@? z?9Cs3RFp;8+~f6q=|6 z4OhsPO|CFn=Hztl=Ua`c|BkCMG~+5>2^>z`Ru^6iWIClELZ4}I(6e@Uv~TE|_X$r0 zUb}Qf`j9h|K37858>1o`^Uj%ZR)9iXFYbWpNBrKwa8NM1hrNyJ3+e!7szl)i6W(P2b|-2ARrVd@g|7R zBSQKs{E;*#u-9b70U2`5w`-bnYpsehjEHj7so|7!#&K3*qS z_be91z(I#FNexG^>rK+R5qJI28gN}(-h2zS!m|3eB7Q#Bch^(ECg#Bo48if`$@^`4 z{ME8d@rx4|2YtB|6C1q=GW5g2Q2B1TxD5ib)U~G2H^~_ZFK`Jv#rUES@c@-AsgCb2 zomSBHBPTQn@YhWjH%cGKOX9g@2?YDNZ<&3PMt#z`k$#N5WV08KFm9|^qIcAWnj8b7 zlTy8nBd=j^&O{^R%(}1in{g}BPsFtIhHf)2$bXCA!!vsG8TvravCZX$RiU^0wK+dS zKK97q(3xq8y?4l06TB7Ze-Xipr$YYKkhUTl29`36>{;s!!gMB|A(|^^mecvOCJa%e zP0};%Ce#gO_ZXe)+|vWRV{5gb0S_Kh68r=kzPF)i@OP*MVXZyW41cHlQxV89Nw`&I z%SxCCzdw&S>O*SDG4rbZ!_hl%uIoGJ3v&=j88$qERse-D%MMpflJHGCt)EHL#Yf!!n|d$rXttMK=tNA z!gHf`6>vx0fO_utm21~e-jjDbwKXO|xWRha1pm17QXAZddPRYnp%mH!k@Qk)I=H4y ztfkt~TxeFb7Lv%;4=$e(*FL+8n~5`!aR}?f88DLDYjAV<>++Z=S*6kon}p(kv;p?~ zw3lZuF&T4t4m29t?Tyq(sSi>H-x8w^QZ`|^Ccup1-l~=re{D^0>hpSey18U9`&=+K z6rwQBXDI4{pJipr(?RNhl!lsnOwgmS%C%>rsuaT87_SWqxY#pS5|UJ%sx?-5LwF4D zBqu7nwTg3cv4^59@mQ?=cp@}c<)Iq)^dO@%osiZ z));)zL-Fo<7B2iGn4~MQO_DCQ71#~tSq`)a?*?m*{>6ahzSKWY>!ZWKrKEz))QpjL z$&WY>)bipJlsMMizSK9F6~6Dsf=z}TONOkYkl8s2D_(cK^lm#Uwg7~kfv3j?A8;yT zg&TloWq>R{HdVKdhav?03uMynScJYImK-ql7!WgQy-`Tvdk z5a8dF#}(&|5n0g*OJVD9&EetC00D-ZCwN`B`)kr$BGRHm5lAo^?}%+-E%vH`9BRNp z47+O1;UMdP&L%#0!eXobOnh0vC9VhcWDKPfwx$uK5D1Oz6~tpA0^o$FM)poZjAH)^i}Rq(o4#o z6yh(tng`hceh^g%D*N`i-)zG6a?SZPLL~U?^oia5M!HGxu|vBV3z`l`2RY^P{f^r% zueT6>qOpg$g=z)ci}!5>3kPenmCBqC3P%qu7-Jm~NeCu}3Vb2%bsf@74x=3>M(!UC z7d9cfBS8kdjv37kGe;aV5e2!nzcuL8$Feh5UBGlehlgALM8zj`@Ccwt)J}q}kcg*& zm0WaHjsf|6n(;R%yhb_FNq*xN+WFX_d|VVSKI5&BRE{mc4SXx;ja|@Oiv9X*lpo;g z{}^}1FO}ygz)f`vF$3o-GL%X%0-uC-D*@wE9dLO>k?5)DhW$tUik{k~{QhgG`Vh}n zqAH#kCZz4p36U530wzV;$2~>j&9J>vT?fO9&SA&tnzn%z%|r(zD@e~B zvI&ge+A_Gb^?}Ubh7|%QELW%nP$x`?UKlDB-H=JhI$q1VUecloAseJUMfeZ6Hcx{S zt!tt)o>bDp1gmhYCv8SdBhG2-(Mu%8PH+yaeWM^xMFvb$9)z;O3_J)2nqoJBJM)C+ zUm1s;+sQh_^1rcy2-GfWLA#*&vX-qFK%|8a=#ekhHmuH0%m| z69%%sTSWOsN$V*QpD%^&?P)ub3*}D~1Ue&$2hd?ye7cK9QYmhv(1bL6OvPoXfnX&YL`HH5DM@vb}ZkUF58;`w4U)BK8n z|JOHwkV?B}Z4a|T!HZKNu;g3VLCExxVH6?=IEru$;U{wT*3V7IgZ zD)53)0#HtZQIye=$fFBscF92$Mg_@06ksHUryNa7X?*Ec3IB$c?f1@P8t4|Vt(xmP z47F37JB74d+-lIyTQZ1KM#v*)8UPFcHmxAGfUrb1I_+^HejO7WnF|A}1o&(Aeg@kMa zSOeG#E{Lk4e;F659M}TBRr7zRe)Jpq^lBxQoSw1kThbW+jj6%M0cjzd*}9n=hq?AJ z*M1Q&D1@I_m98j-S|1fzbL2WOi7+Sj@n2AwpM)gS9INrk5Q>;rjruZLsC6M#;Oa0{ zs4GZM>tlWzaz&8D-hp7XE*K^EiU@ItTnAW#s!Lk3C!B8|VRprX*U3=(v9I^%zEa1_ ziWkFQRGcY0znmCH>g^mb+exIb*tz7KJ@#+@@)f_jI^{DBIQ^ZTHvDqhvDY}d85doj zX1WN2W=SDMD1}ys;kMi;vpt*PMF$Zuai}$7M23>Y{|R=3-2v7Ic7p}p9o4R;6KKGs zzA4FP#4X(b|6 zgd_Ta8WI9m2q%JNrR2+(rM$BeU>uyiqL~=^FN@i~9;CuZ8@cOIkRFob*7&*cjL3R&@ND$BJSR&f`+Gd3iTu$opi25V@y)-Bs)Fc z-C}XYx?D7xv)uqXdRaH?J-dI?_QOQ3m-{F{CSH(D~Ma4+Z5I3Pa$fcm26nRAjC3p7G2$OZV}1$g9MV2C~WYYCI7^o@*r z&_?h)h$2`6MgzuYGsbC)7);G-valOR2w?$%#WllPy?Pu}^E1lj8L5&R2RWfMe}c!` zxLf!&_o(WBmTHH`xm?_nbvRY10HJzNI#|#&`_0@ zp^DaP4>{$1Az$iCRI=4!8KYV@IQHK|{f8AX6X>C=DpS zO(;CY{}lljmYV;+vgmh`X+yaXjr~_BXntf5;+qGo>ya_QR=BF3T$6)f`LiP)qo17q znOH*i$@9p@pHs3?rEptA4uk)NmpO;*4pHMy8a|S&H~Y-=$OI?LAVQfx65M+;QReA*{0o5us?o+ zz(B|GlM}xaBf*@wkPh<1sv2$&Qw@DcqRXwX5( z3xxM-nD=ZvM~N3;6JYnYEcN7I1e!yqDj>AT-JZuio)yB%fja%&AZrvT6*IjIpUyd) z3mSn_nm~lMQ!KM&lbchc`F%r(c~EM5LqF?DCLjNouVoI{W!LDKY;r5XFj7@GBKjR| zCrFgK!4qi*qJMn%^6(36+&BIM5HptN3jjvFk?TYN42AulPr?zw37{GMsSY&d<#_8q zQ%JW8Jes%bM2t|UkeEsTgL5-nWKOl7(paoy-ttI1R?7sqh;qc}fCp}don`};lh^&A z;T)_Q2wyVSlLqikWNDHg6lU|AsgbCE09k@7)#z-rWy7>JvMG_u9B8rgjYzY3RMNw9i~ zNz6x{&)ZlHE%3TH;}PKY-|o;$c17dHLtjLVDYkoXCTuiqm*y6U;ipA85>NlK#wXT1 zNk(!Cg}H0VI~DT_vpoau5-zq&ZQ_OtWQT)_%5{GZrp`FH{8AKbV54X9DRZMQ*A6(W zg@E;w3?7EJoUvI0&w36!qFtNZ{Wl)hxWlOdDg!Jkpv>1O6Bv)UorIgYvq*KRyL@r4 zoyL{>Y^QK1*YFAxR)gyd%ZCYR9;cxY7Dq8=Q=2M1)s5oH1V4ZzRn-xRgsJ?BRaeqn)d} zfxHx=Vxy%iSE(lP zD8^7V4EFt`tY8nPyvdokc-(`Lk3)I+*zPPks$sH06xG3i08x(xtijrC+ZMw52Rr_i5QxEBcnLh%zMzIn-mx)*JgUWvkWj1HaAN42cpNWL6#nq?np zO+gQXf8TYH8Lq~>96E{=bE@L5)&%SYBGhb_bj3#6Jy=Bl#{G;JlT7ql5)$ptHT`ga zC+ZkLZbFv|^Un{4 zinxDD+m7xgX#TK7z`4us_RcTN6Ej{M+>gDZDr&Vrt{rwb_iR?e?~M5e^({He9po9 zOojAShDWxG=j}TEIs5$nTF6y*MlEw5A*RuD+0ls#V@KZhYYZ$L=13A3iOfQRkR9j+ zK2T&e#=sqCgM4SK?xtapo~S9-efM$0xLy(|k;rT&oyJ`I#SdreP{X)v&ggnjE!EDz zG$*Z~h|WccL)juabvPR4Tj+L5d7B@fGQf01(mR)<$xT_12R9JfsZ>d=htdrtQY&2` zyBeT1XG|XRKfgK_#8gnz4n4QG@08#{M$cD-C5;&kN2!d9d4Qh|;{caP4O3ehrwv)B z7Mx8%wdH<}u@WN{RML|0u44M(@oLI+#IIDi|$>I&5sQt()v0frKBhxc~z+LuS+u zs!#vRKBnrqwDip?4Y<233VVQMTtZtWzi=C*=X#QR3Kek-y`xX*KrMckP9aTLT}st};PTUBU`7-9!XNhlWU$oR|CsDAB9Ab~Uh3mlz_rKkk@b43<-RxzfMv zyocb+fv=NuKE||*9q%G(n}sSn4=`j#5hu##mCa(?+Ap{Xnqz+{vHSpI&O7l$R$hzO z)E7y6{(#^EorFb}`$bf|$@^NdrJu5bf=XPmg7VLI>{g<3-$d6cdE$J})-2BqItl|P z6K@Gpf$=ApN&Q?-cQh`?rcZg`26ui|5`Q?opp|@COHC8|GcVrWavGuHQh7Z#qtE(Y zT~Xe3sOXr@OR%lrmcXCY?1;&2`>_M-GULtX2JlEw+}oa?qf_;b%%mUu^8U{3>rp3^ znz-4!zL_WkrWPZQtK8dFbI-pd*ONsU{U*_VuXI1%s@R-(8Klf?Q77lj{mu-g$qdE; zwN(eXxN6Ih^{7D83*qt90C95>_;A(8eed;UDYRCVWzrmwZ=3UMt9_lDlUijwvfHyw z`+K(s>`@cVv1E&H6r9fc`pQxeqlAv{glO=A4|u}tpzH_80T`$_Kn56|OfbjX^RE6T zaBVadU(%a-!|$_MI3X9lvOXji&I4<+k6!170eR8nhLK-Q?u)Qd6NG8ECt3--KQz^T z?4$>2l!2rfwX0u|`&Zg0R!UcqT2L#3lJE2w6~6|D-!6#rVf8jKb>{F^)Oz*czOp?2 zRY$B;-!r;}uYK&I_x&gZe3Qo)CJ)`w%%w#yvqx)jCLFv4fz2lYkpFZQ>JlvPX}$uE zq6(v7woxnc5s>z<0PWO2aOG~~cK%Iu&v?YdVc3TJ*ERNbLFyo{xb6YJD)}x5tmi*z=80Nwg4xLjl7)nFEL7~Ut=iC@Q@fJ!bae`;tu z7Z0kCJcK4R57=Gun|lTKnZE|4hx0A55Gu9Po4t9nhqyG7 zC2Wz*{mH58_kNoojae`B9XoVTu%GHLD^AY2ZRip0QQ&jHvV+IJUW$9pn5K9ro48yo7yFMR7bj=pnqX>Go%%VTx&HHC89(# z+YPi=+jn$jAQKn3Y=ku*LJEyI%Kh{Yovn0!;r#5kZ3LQ#H8&N1-4{n$TAjkRW@Zz_Y&{i{*!dgAUiL6fP!CLyx` zR@TeDi05u<{;or++|I)Mdzn#kx_=5xJNAwtJ7v&q{8o?z?}xT^;?K+$Pbt>3BzDW` zeL16UmSjo=`y}uW$%jTGb`exk&y&;9D&$9d9GF5T=*H)?3l81lx%VWSJ?eKHso#F^ zZfy!h%fmI@memIz^JPIgl}R+$8~1DY`^T8uV~!cc`49%1$f7B zE7h{@7);IO2XEaZWKB?X1`ut<0-wZCYD6=1UlW))5STeOGqZ|&un9edQtq{!SYJOF zptI%(@+8oHM=HcvId0uSChO(4nONL7v%YrE9kAuu`4UuC0gk4l^ z&tz)%sa>Us~t1CV}Ac4pOCc?5?5w&08Q1pgkw5A1g_Q4B?T4jRVO!} zOiL9t`~ci95b=(n-HyyHC({N&Jn`<3>|NG4=NKw!L3&dWT(`%&leHG7`6Y=+qd`(;QD>Q$E-Yq4(Z{ zVL{cwy9hmj4tE7kF~ZauJ#cD%VikJp1TpKPO=pIC(Q)HD6*p1t2o zIR=OPa#<}nSdbO`KJ5ASOUNpK07bow{v~jOKxEhoi`NKW8-hsopyYvY{>vb>(O^>p zoT61tQ%#_PSIx4;MUeY3=5jx7F%#z?v%2eA{YR655>E-$m@;bc#rJFh8;$zXJNV7f z?1Hx+`&`WJsFYbDqw9)#OZpW4{nTD1n-_}-78ZdUpp>VAC@ttrSLRaZoA<5c@JGGv z!+2^^()x_R5*)M(_&Fsc3A!D8`xakaw6N}IXE_0vrpkw5rmq-6%1+)hDVeQ)(sF9j zZ)H2f`8IGANcyC$v1Q}vsTBH@V;A^vXYrs|yyVqTc6;<5R_#~AqX3_z5l`UfLihOZPCZKfYPm>csDngPmRNJ*K!Cr!@^4 z+d%8!@Lj-{CrAD{OfwbapE$S82rGKXtxC~)DN6piLAIf?hc5uhT9B))u{FEsz2*5qWBA&-T~_61}Yvbu+Y0yUAELC&lXCvp{@Sw2!A(T1~ry zWV35r5a4m2=7Q*n(FkdR1aY)5CLlVt;U{g{0kNNeUe|R#>k2*B$Ua-{@Ub(?&+Sm+ zi(BexsJ=3j>_)m1efHStKgk?#Jk}aVxW6pzi7Pv_px~EB8glJZ>A5f zi&OfGQv9HdJMp;Y(2xv&QE9t1UJ}yl+VH9s#q%hxWmHfN3Qk}vjVv(W{!l74$}5DA zq&xD4@?|{>Xuhx%4+>DM?XQH7W$2Gup)|^luzf<@BHXqMsFstknKxbIaxX$z5jQ{! zoRo9qd+tP8aX}t}HJsFK)H$`@EMFFgskS~TOZN7xPEGId3j(l-ZW;H*ploe7`SEgD zf~BT&W{1bziIJ32nCoskFbNgFnrw*d4QUws9PvpM)q~j3k*eP0jMO7)x}IzLx;QrY?Lm%* zJ63~o<)t>O`Eyz8JPL#ZbU|`jvA8L{s5AwK4S>ENMuPe$o_5&iT!Rb~j`SS0;~;=c zM>p6hq&jxQ!1xJWcK$s!tonV(Pw@k#zGrcSe#Q>-62go(=*bMdTswF#3!Uj7$BHlw z<+iolT{mrr%2SY|3D+el4Y*2BB`$TLDm^$LwEUA=FMS0@`zO9N^(Sq0whX-ve4-$X z#x`bkX`Y8+W5eVJWnOzAiNHfE$ek^Tf5hRq*yjYas8Tg}&9`LW{XN#~_PIJby`K(~ z;KknY%n$0ci+Ai=ja~j#dm{c?MW)%AHHTUli>!c_rLquJ+MZUKb@I443Th5!f9z9I zNh{;mJK9!Paz6M<#8QV2#`Lz;wKZw_zsjjElkl?&kz&e%1wPmmJeTA$_^=7D8lw}% zEgV^G_qAGKy=oS2I3oddiNYkguP`T|ouC%2VRmGnAtU-2uOa{b8qTj4brET9$=nhb zz?qL;ucZ2S58t-8LPv|qFso%Xc?m?MH4rrtsZXfdw;Vqiuue7KmZ?8S{p5=-_?}rl z#ft5g95(5fne*Dcmb7NU`d^BEG3x%B-(SZ#Pto==jGIiJyA2ys5M~^) zAqGlI>1}~wxk4vo#Di_OLzzJt3Jt!X1;xqk%wJ->rZgL|nH{Fgt7%&Kk zJ{UP^@Vy4;JV&(Lgh8kxUBM*04Y%npbd&w@^(%tYxAGnB)$q$fO|YV!M3R;fJ2}m5 zqE^(ZH&iE79x78+Rw!J%-u;YEP+6z?*GU$Gx9zi{@}$^F56f*k=PjS?S-&3LFM(!5 zXh!!-MNb=S-C_!_+V{xQtzZPOQcpT2bjY%#jFOhe=q z_JoEW-=@Z6dMhAc+pW{oOM}rE`4ZES{?d{#E~J|V$$pz4p1*tw<5yL)fv-`MiUF=| z5qZv9%X*|5Uot?)jg_gU)k+tUchvn|+p_o4Y&61ssdhko-Z;PJO@(F=cSPLLsQ*97 zO*>u0KyWODkC>&5$Eqtc-s zUydeXI63e-EQTM8{iR~~L=LO&#qeCga?xTN2y??=TPubu0$oY&&RzyeH49XZI8X^ z^Js+M2YgGVACkgtwAKZ%w*dAvTJ)nx(|`tTT7!0fknHjJJz-Ha>2Qc=BN^JooNRPM zyRtpL5bSHHtt2%67A4`K^ZTI`)Mzi?eDjXBg+vaC9C*?!2F)A!!vD9}|1XnV@}%V9 zR$97nITUr*@C(G6=<_#10X-1gSa=sM$JC_ik)9-jyTW5}W*OY$OS#7$++(m?4BuAT z3e*ax@C+PZ6vv}8jWNRRMqu2qZMfnljl5^q8z?pMtm#B#tI=&Xd4=8<@V&KEa!A;g zMT|#G#8|U#2?oJhgP_{3wz9=*v`GJ!P*Pal7eML=@0+2WZVj|+jtM=am4nqc#cB## zCDmjLnFKNkSlgDhdB73<{cI<#0EG&^?iR@y4r3E&Y>mdeA{?`ZT|%RJHpLb-jF?c< zRm}t@)EEi#@xAuOsA0Sjs;f%7TWGn^a-rp3iqGRbD{hReIQxMs)n{|sE~3BJt0-8| zV#BSWazWAF2+;xS!1`se{&1$chdLD-sm?%J_*`9hj>rGc;c64c&DhM1EO4RmMWenG z&V={Owm*i@Z6uZMpqI310gm*HTYx{scXOv72^vlj9B{rUoDqe*E!Ik*sRdpncDROH zAXT^TX}QNKpn5}4<&!|v_Glz$kb0zwoI##*k-xW58CdvCs0_S%6D4V7ATxdurQ)|~ z4QiTQzdddeJgvgQM(B5Ky5BX~pkgM;TqbMH*yJCQ}UaT~E5)`hb!F4zlW&5RJ9KzO3Z6qiv^ z`h>DfuCM`od9%Je@l-3WJc(slj^?^PL=r0SpK(q zt3l~H_jA$#)K)-s8KXdD`&M81{Na&Sv)b#SvZnQ*bX}uN^AY*{m18|kEdy6?hO4yM zpkM^1N_5b+M86rmxMh0*ga=K4d$LcMlnQWDB14u1INwsxv)u?wt8TS%6)3Lb9V1?d zcwy(gy{u9s)JDe~zHx6C0{g415O+>nATAT+CGR4p|=7^B4*S z6bxYZk_twiY3rdSs%zX8X%+?Ye%nNGGJ5+y*cfVdSJ@E>5>DY7c)l#2&vb^oYzLex z0IWWu?ie#Gn+eU_NCNt#OpoXjkX6Vl?U2T56oonhA*H>+eV;T6;yh0opcv@QKyM9j zF8u$A11O*U{fK0arkX!rL(_H>QHk~{0RTy4Q z7MY3#mUo2q0qp~9Ze5&4*o^WJ7vOx-s_Qwa%4}4LILuYBvwgl>-fL`3lp&zgVRZ7< zBHYSmS~5GhukfrhYO+*}5CD-3;;8TQI~t6-A+-L)4WA(m`RrB9)~U^MypHp^B-OnH zv=={n`lAu&ksLFjb0Cm|O*{|T;w>RrfUEI9IV(91cmnL*ZQ zOoTJBMsefY);bIz-*3p*0=uSg=R>d#S4NHp`<;+1f&gdL2ITc29eD0;;TvSOj?8=T z4H*P7qIh`)vO)mO6Et%t`M!e;JC|VzAodi9^?4xW6%ZW|A(RLUWMcIt1z~4gO^nEK zn6!1-7>DbTh5^X3ko9xO`kD>jkcQC+vUF=ewoi~%NW3^-{5D)A!N;Q@Yffe@Yzxg5 z@kmMaX`*LErwh$wluxiTwT4w)95y+mxwWwwH!7V}e4$dxUJ;9yp8 zAk|WpR5J3M<)}UQvGR7sB&)TYN=9x%=h{X6xJOIKZ8l*TqaNKrKc{+JMxB=Od@G?0=wu@$;E~6Frv=*O= z8nk1P&(Yo%I>CegBarl~kIkJU^ zfr{FKM|neN9dN@1Zg}p6L+OChaYL{!^Re=Pvg*MsgvXAglon-(H52lf{zjKfO2L#! zA^tHX(lz`mH$D28q3W>QOn=nMuJGdF#S0>q#5i=&;SPR0K;9>iQy_{TZ62|dtcC-R zGIrKU&+&U9uiF_8qgTjHbGbeV@ZhVbZ~T4w!h>xXe_hVeSj2b`liMmD1q zxU>kDeEv4S7QzK)UA2&zC#-DYak7PZ?^do_xZ?4mS{kl3_rUKpl7_BO?H8s4L2K% zS7f{rtmT2iF#XI6GxKB*d%em2<|g+-kP&*gAjp_^H|b)=ja#+=n70e_Ih2P4ghLjJ zGA%{81|B?Q6eL|=(C-F&on)_ybNxPuzl7ibS`4l7ZzGVZ6ab6K2y8kA*u>pz&@L?Ct8Y>J!s9!0>vfU>N(?FBW3Rf ziL8*oCtRmB(vdh=0CGDQoPLSuq{j-W{efQ(_-+_3gtZL39w9mFWqYji&K1~91Z<}w zLSd+*fU*9W`SpVjrd?Gz_Mv4e#v9lRe8s7bWOKv=p;Zg|GmX#89TVmv9*B6LgJhL} zkoB0$YY^xIJWtgtIXt~Pvzl_fYdRW?ZDuC8*kdlLMDxmPAnBq2utgB>j@1E1q-y(` z6@VDJBJ^`Hbj1#y*XzQE6^PCuI>$%nh{t<>LH-uN<_LeAp_Xn9)b=qG1Z%|Jgnz`tH=mTv&*8Pc-BoMb8YWT{QG5I+fioqxKfD0GH-G#dg zcNgw1(5iw~URp>mg@^#EvQ*8dPN$HErF_%F7O(r(40TMMA6njj#~QXO2YX4 ze$Tf-61@5TF%07Q%cn4Y#SEz~xJ`O&Rf2xxIm?+|Gpp4nSSkno%=YJ`)eu!1KoadF zHXt=gGs}#Cff^EeUtk%oWf=;nA1b0+;))mcf+P${SkQLDKl6FqK{HN>!a!+HP+9}S zh6qJ!F%Wu0=zTuhde&V(V<*80IQATld;+m-732f6@`2AK4g2n74d0Jg0fExBEFk!1 zRb){cOVWfwK|-*jDG<#YHqnLx8iGA+S62=Y%|kS=2i0{5=OLVDA8-{q!WR~N$^fey z;+w}|db@r+t;zQMcxqJKpw&Eq73qUmaY4y*)`CodJ9lU9)Hj8+k2~mNeq|UHQ)8qJ z`I#uH=9m_^Z#C}Q1Y+NlRSJTCP_EH@NW)Mn+d!z>J$K0WgjE1!3$#KYF)NVF zm@@}?&UOmVSch-SsNUozbgs1!UB_;elQYkRUfk}?LvIUsZzJ!UInvO=p@pNe?Ak_Iw-%aEd2!@7LZ0&>DR0u^X?7TZe|MoA?;@**LMnGBb)y^vsP?~(;Kg?=eEGuvzr7`g?(-gmHb z>3pR{NaaH+AJ6T)PDVFMg;c&q%%xj{x&36WQqtaj&BriFmh0!Op)8N*F!_+a#bvO1 z8-5OUtK?%wpH>_v-$8J^#Bra~{(a&V(QhAh+69Q8)?0>d;Lqg(^13UR3+Q*B;4Pzb zZY>J=beFXjI+BWz>t4xf7^u-9w+E)8TT@Zw{E*``=b;z3fr1SMy9({;na>?`=Z1m} zr@L1qRl;H`q+;q9kb24|%b7Z&+{Dx`WHGvgOFn&$Ukk0+tj54e3@&6B9?8mA8;+RF zt`6EWl>~-dNWcsTp<9d@5O#9Dsm_3KAi{y@>~SG3H$yPp8VK$?g4F_p%crMk{U(UD z!+}8@2JdMly7?V%s@NO$t=7IlG=^ww1kI*ftQ$l9iO$0c3#*SPOVQlQW@U+tY~VuL zFc91d!BP(znax6WBeU5^FOz))THeB!8Ep6X;x=vYLaZEY@%FUu2VhF?#Bh=1o zU_GrV!`H#ElE#uPJ83f?f_1nuqKp)Fgfaq_Lm7cG(zSQJ|4*$hSKV;B0K@7djieFT zO82|o0jxSsvXeLkGTS3FPvrC|6|F-ipQ*>KY$P)f_rIbhljoGn+?1wMU9ikeKv&c~ zwv!+P49um8LMJ^N3h~T#0i;_1-p%1H1F@HreZ)_T8|Y1NifVGQ{JfkmgVir730A9RdQ<26L(9e6LRSXWp(_iC4UxnI zvBzg%YjyVOg4ZG*N*->YFV`%G$PCItWClN#`I&pXlisDVwIgVq++4F9Vg!JyB}O>o z%8DDf!L_}V7?Jz7o>q6P{-BvxhlPtF5rfK*h#?X8k;+q>sVo(Xb0lZ?>yPF3%ZE7n zd|HR5>ct4X7Kp4QICRXTBnRt-)d$hx6=qg3M^_ z+=klw+#zW`J}p?iW@BfP;p7bD4YWU^w*}<4hWtFkrRmGtaFt${5u>Aw<xQ_lEtn1vr(rO2>%os@(Yl3O~oqcz>KjP<+j7h+$;_qJ){8W>21^fkL)(-siwgCLLfM0@d z3GBDK?Izta+HDV~ge{QYO%@UfD^}Bx+!1(=>lt645m?6;Re4;Lc?<-veds(kVxI+h zDHVw|0NR1%%8n?KMd_BHbSpOsK8j>(5U)&c6nNPI?=mB+k0=+)aw}0P3*Mfx=|;H| ze%D=Lmy?(UN?k!IUoXq8gd&2vtQHbDANm|IDKDtYLR}V2fO!+pNDfzT-U#~NX1|1S z-Y;Q{ehKK806MpzvqC4w%A)t-dS_m&ew)7i=-K$bhi##RLz$8GZqVn++2hIwg!=V3BH)P7Lfe_~M+By6gY;{^Fne~W zZi<5g4i19q;M|^!&;tht2e4nr{whv|4>mY5u#0hCz0p^Qh82EZjh|`2aZgDs?bB8P@y9Spmh0#3hhQD9%!nj~Eg_OXBmt}2vic!+ zs2{*;D_LEjq2QrrB8hCN@&FO1*y=J-LCyBk#KW1IBh5ygpl0u>$&U(Qmo^d{d~Onk z@4_gIW(f)|&Z6*4q$pq-g+bR>I-vHBnM&?!&SU}gdr$pZg}Jy83NsXDD9o4gzjKAz zhic&_i4`V+ydEU!Z0#Sx`Z;{N44&iQRa<11?2FPJlk@lkqFv8w0$F1@*>2R zFjDrovIt$j9;2+m-c#9R-fv~x-IP=(nG3GE##LUXu3z7#+L0)cplWtGQFmsXoIjI* zk9J5G5ZJE=cQkv(!u>sHqxgwP1qbZfBM305m*QpZUEg5pdTP9dAPOKU0DvP8|XcvcTCXR-2gc!feWa*1=VIk zcV#&=4YQiYh=_+3otr*sIf1>Jh7{h2=}1BEL-(!yT+Ey|Aaf&e-d&^6MC3j$hQIKTYY;3-^fwWaOKj!?BBtzh^?x33VF?KGkC9jy8oX#BGS%x6D>v>E>2K)G$1{hP3&q_WdSLS`Omr!55Zk-1kHV zsWe^I?1a=vA|da?ZmtuFr0|*ADcp1m7zTF@L(MSDWNAP>IlT1#J$~5#p`ta+is{%X z*`OLbb;rN=e10gJH)aeAy$=Qq3w54PhJ^?R<}DbM zGyf~5MaXZ(^qW6}Rr(3Rez0HbNHON&Pt+*DAMgkK&B0#?aOM~hE}lcPvyv-2VhWH` zumMLd!jW^A9{+4N(b>FXU(GzbD=@4+qMWb8t;7f**Ki7CwnpaYOiTdNZD8_AINZuc z!yrgMjmoK=Jm(@?tLYu&1KUkra%!H^i`$)=>1_eJ+oF5SD@PM16s_6x2CVNn>p2;_ zx(+jcP|S#8#tTs!Q{`R9uu>PqY?;}S^7$M26g@xh@ojcPXw%T9!F>oVw@LkCP_fL$ z$^$Kw>dL}qQ6 zPP4u)m|0whU?zf@2xeZ0+Q;Iha-kA1>{CEpmpwd6sQq_T=!LY|}F zBC4WPFzgM+_4Mur&`dwk%^q3DsP44Ad3b{iPs;fHV0;uLrdZFLUWg*{(UO99N z5Z^}P*UKd>GZ^7X->6)Yw>kAXaIGPiKrV3%#uv?ETy76@?hccR-Y&Pn(<)qGP6UFU zI?$i8e)>%AreAVOgo}L_45Un=??V60MxM2(#BU}^F?+N7Arq&j06!A}|IIE*qP4b- zQFsl=@{r}LX5?OEG9b!Bly?a43UE}pMp?9PwwLnw3BcHO7<+k))Yohcg^?cstplLt zhg3A%3pxvQmex#`CYKXVBe~qH(t_l2SO_($YuE=wR}o#kVwtBD=+1cT2(E(NQsPQ; z)z2>r*&0fTDCH%0!95?+bxYL*(sj?7%V1FA^NLnLVg@+RBhm8~eLl_RhR6(&*;*v> zGO=g{&iiK1`=l(PA_kM^ET`v$7lWtFs29z4sExD(&&9mE8NIlj8tftQuKz6O1l|04 z$hMGe`FitQw;e;bg>DPTGF?JQ*~?4x_s4mdzT`668cI$M7K_K-Hi=$O8^2u!tLV9_ zeQ#=N8thxECf|^Efo2nf+r0j8JrMZ5+vhEE0-WGmchAzOWIRv3R z@W4H;@^W?k`bJ}JNJof@GE1HVf^QPRRRM#1C+r3^5ikm$shxIAw*cW=yUu*u0;<%N z11MUZZTfBzI7vpf`c@}f0{CWu?~F$d#rnF#KXZytgG8`8Ek2aee2CL~9A_v*3JfwC+hj71Szt}Bn#q_OZ5PYhF?0FKM!OFdK^5#w zU%~Bh;b0raUzc+vLz=b(Bd){A$e|V|cQw<;J!a~|$ZT>YC7U3zoxBl=J@5uI%j`kn zGqp@?r+dI4^wS`Sc`eJsJYM{BxJvB-CjA&j`-~g)Gb49SdDhR}U{r4o`ZepvAKkIs z_oqyAt64qwjg0#l+dS`w>OQRnI!yerhwT*CHDuQh6r6WLj}(;p3rhX-3aC*B-DHtM z4;uHJ#@iu-$T~cue+r-~(9QGOk14vRATi#3T=L24q7~4g(P+DcyXx^`1d~F6M%!0U zBSJ*dcFyztSOxn*b%28`n@fp-V$d9J46Z7GRByTh^#O3L!Zq*RO1xoc56~VQ#b%B6 zz^;V~0TrS}E*xK=?2~PvLO_K8@eLEdVQ)h8fkhwK8ldMsvv-e%Ns&ph6Ey-{FU>ZwDX6;429WxR+kA_d7wcSn{;6tSDF0KK+=+{ zd{1spMHso4l#-A&BIVJJB-4C zRl_J8C?3Xvd`?Kp%b~^(Iu3;~!slon7Fjr~L!-pYmM+6|8xR$VbYP__E4{oP-|xjR zgEp+>GxDU%8nvx`ra72J8k=PhrdehLPv=6z2^h-P z0$VC`zXqF6aTxxYzGwYe?diKXbh!RA8+`C#xd{iG?0*{B4C2yMYzBGGTAP92i`kO_ zA)7O1)xgPs-WHhE)@GG&Si{Zy>BV?7YBLDxhdin!$Aabdw{-Jg!{D~5p{{cZWW^S%+1m~ewiES z$|TrRTN8$u(L~Is0Nl?7j$;LIw&SNqVj|{GD6F6WyZ~?Q5%q~kw)zR z{mTq*K>uhJ#h`yDJyjX{ljkg_|DheHJYY#0>Qq{$UXcDS)}|ULqCst{3-m8BT%{H= zTl-t~s9|Y6+Y{;5NVk@bMlbi9K*BY|^;-dW21n6jZN>G{S~&1#9e8dg0-b$eIhXli zks~JK@RB;cGiXP~A^2aza8(EX_f7Uu4o$zerak$>=E#=0Znlg!xUV-i7tfaQDC3!Q znZlR`74D!GN5uUG!$?rdtEuE}~o6KmRrSYG4vhc|%C+ zkk$pAOlG-Uqcy+;(mMQb`(VB&W95N1{&smGd~CBmHXRvK?R`t0v>eFyd=J0}Qf5@? zs%f?v6@^&UV<*N!0(EaNvH|MEW1rEB+llJ*wt)OLlb_9o%>G_An zpi0Au9F8k8ZTSwF#xB0+pjJ^{vKFzY;SeSTm~7v{R((tm>_(%b0e3QR#%_3H)^2dM zQf8HW(Yc_B*fY%o(w5i_B-`AM-Jm9*{CElBlEc9^ij)1-;JjhZb7fJ!J5{kGKCN=h z)J6Gb%VKL#UtbsNTgUn-y|^XoQ5b6@?2Ftk9T$oRh9rG<{hTp<`xxy~lA1UzR5wM) z79m?{pWm|6kB}`wwvI8oN<`s<4Z^kT;&ziMz*q+vJI>Q$CTwHJc8qnIte~g0^sJjz z!7hf{foQ1tp%rAC%SS_dQ8ENo8`$p_`+VOo!BWh#Kp7EF^?X34*PZ)}G9u)sxI|=y z2^XgTq=}M!!8<^T3flQfz%X)O$7%4|NPS!b>PvvCB>_= z94POZW1gdw0b&D$BJ%_YJtA~6J|lG6ohafYH~~is;K=8gh*m(0HmgN*ja1y}qcY;l zB{?nHY#S9`z;IEEE_^0v(YHW#HO&BdBgh*;``HUoxEXNi)&Oo_fqT&mk>&Q?xA|(f z2#@;k+vPTRT7?T_iNG`0f;?Z|&NVXW-ZQ^D+XN>gi~629;);DX6sOb>MRrSU$a4GB zDqFV(`9Xe=ALIx5Eh4`XyQSnWq=dYC-6Y>ZeKaoAp*}Cflb&APwk_nDU*F2=CYg^; zR(3GImfF(px4UGyGNBb6U;E7|wJ^0fFc*e90&v;!o-D!{CN=Kejs!k+ojLKOSv&kW@DfbLhJyA;+ExbN1> z&)@%@KJ^&HaNA8|owVz&P@Ozyxzm1Z`6AW!iGW{*dIxhyr9OLM#G4~7y8&j?+X9ch zk4zUZy0*A~zY{7oQD!8-^+WD&Q^cZD6P23oVN?P_HetQI1~r<1=c%?R_iMW-b9Wh; zz+#WFs8X9*UIVqjun0^7FfIrACVb=XDJKGVALPqRgZu*PY(_Fw3<_7bOb4WUfa?9F zdVOu?VFju)QJu+a29q74WPhdz&Ac(0 zM^EHn{c|FFRNV8RUw zY?WErnzaZLg0;nB_pZjYyNp<1)N+g}2NEUgp;$n%Xi+zK$4VAHlY)#{!q6UR+ZU4r{O|bIm2I;E<#9 zllev-SMB|jJLE3*elpO;1HGT@B)pRI{re_+tEQaq!)|cC;d~2p@O&m@7m3?&zX9hy z!TEr~VGS60_q(zRVMXAykd}AutJ;Z+>?A^gNsBO96F`)#f=U6EqJ>J~TcJ|8IF+FA znJ|^$mI%+Q6v@l(_32YE|6(XeSlSOt1zhW@A()++-$6$@=oG+nFYsJKVfo+6T7`Ta z)CwRKY6aAan}Q{;Sjg8wvw&t{UbA4jCn2)}nHAuCYtGkpUS2H^0)=K`#eN&;6mY81 zyBSKw4MBO&y`c<$vVdUua!3%MMmqT4Jt1fU8Sjlsi zBl931NY#ZAK}1m)`KlIAK5V4(cxuIg;ESwca4hj~(T?VV@ zSzD=IEVr9g@cn=8g7jDY{m}l|L^CTYBiqPRV~8- z?n4PgWsfT>=Jo6AJ^dTfFrXnoL+FVC;FCFha+eM3Xs{Pf+Y9dSAzQ613OS~DK%js? zaaGq}GffmIcM#=DiIdWoLs}v-WdxZrc@w=!2em+u!e9^W66}v`Zh+CIdOk93Q0}S6 zY<^7Fm~=zEX1OPc5!;P@ax5x*rgkbB-2%43RolRcEw91m*Wi1!OU#90t6Rd|hr2Hw zxn35M;qJrTziFgb`6+lv+4X0a!skZ=*%siQus;8YY=Z`}W_^A%kcEM0Y#^HX|46<^ z@_kM6y?17P7gKqqKrfKe5ui6?H{BZSc9-2ubhQ{^eL47;zoqZ*Ly#OAl4}b%qD>*; zK*AANg1nQAhgLF>a9pIkh=#%kl_i;)#poRazMl9x&H38FTuGOy3UXRWPQGJI&0Ptb{D%K6wVIlf^b8;7jRozW4Vfg z#d7`pKG=MSqvw;$a9sIj%1nMLvfx;f5kXLc5*W(C}Bm6ZRx7}R&FWgP*8OsT# zr>mim0S8oKm;XDKMDL+Q`oI}unL`9g$nA2QdFxN3k1xd{|Z!&GoUfQRf)IIFI+-t!x{>1C4y>z z>Pg!;L$$jkT_;frTv~=pKHSEwgffg4KCOpv06d{8eTbc|6iVK3>MX$=Vf zA^eB=A(sJjGX~SG!C)I0%ubTdA)JxpF?}Q2LBV+j#`t}(F%)Dh?bk+!wib4ZOSr z5?6s$9jrqCRrGJ6|C*rowPGMwggoC7;nfc3^xcBeFwR5kjeolaf<~D5UMkr zk3rj$R}T&JS<3=6B*Yv~1%k&S<`Pbn?MlmpM7~}Uu1gLPFK503~{SjY|?T^W4EyMi&k}ZpgQE`Um z>)en{ln-8OvpHG_gy8zZomah%Bp6~}o>y2IE`;_--eoV_U%U!R(76iPkC zD!WH29gFTQgST&>RZ-!_fa!f=x`fkAS<5b2uCz-~_OJQwb(tigiljoYM}|mL=Qw=c z|CySI;dFo~8?keN-T2-b&f0b+k&oE7dwOxpv~8sO88L8oe4doYWp0nOY@9iO%egEA z`RX~t(95f~6TeO3Fid_8zDK)6d$Xq~1=(`_{3iO=%&d^WNL2=bQGrn@t27SU6eO$k z#H9FqzSf9rQ;{lrVoy%yVMy^ltl(KPp+39%0E2OT)u~VZntd2yK(pA%hpr&6E@ehVq(kv(oyF;# z>S9_;(Z1UvjBA?bt{-1wA{NVG&V_J|q8w)foDHlD-?9`xgIR*^IGiWJ`nhGsBiwPu z9p}7pXPogv5{Ud*%Y*{SP3STKhjqm3GulUkSx?L7v3PP{6^8+eam@n$&lFEThpUZJ!4lZs zgzY}3qSF)!8Qc5h!85MK7X2YPgLaNZui${BXz1d{o^$G1_U%kQ|12tzJ ziq5XCq>DisaGmGQz6h(31{_mf+ljH-95Q-_~M0{p;`l zc5u$&oWnVXa}JjGmE{6bOXtfa$bjsG=N!(-bC%OwqpuaY37x|nBTI!aN7ZPrVV)=? zJQCEmO?*T&RZj5-E>mVEDN3?1n(EbrQ~f(eW|^6lStym%Q)R~WsWM(~mr~! zMrqR31F9lY*LmfJ`hgvH(@Ur%B!rnLnF;TXbN7$E@v4S`syYI5FsipAm#<#EWAof= z<}%HgJ~M^8q?malQsiac_c%8d^{olKX;)Ic(TS)PTVX)q8KXnNHEczJ_(L(VC1-%+ z#Tp5f=t{t`ohyENaHuiCXv)+3o_y9ay&t*{waUOwdJ_~bk?1@fO~BUYI`PrD?gS=h z?s@lvsoXRO=(WM6heYf8B1k_EIoaZ`KhUdN zrdp#2(u@ht*ww(6%SeiO{UJjdCr zJrp-#&{B)yrq;5q+5TeETo}dDrqpYm*k*AJ7+l!fUeq=nNUnfq8Q9}su}r_!kHKob zTeZ$!2hEvwz+B5nhlX|B-?2cSF(pohXLU$*G}T!ho#|zL?md7}9sOdugv_#$r`r|C zjV1%;2cB|+;4j%^puParK57>}6Q<(X5A8FB0G$o$EfIo01L98C+Qbhe{#R?iWep)X_~A@@x6R@s8T(O_}Npag3X|5AQ1Eu z4RYH>rkr>_f9PQabLIY-C?>pwCN2ZBEI3N8Zgr%fjB|(#>g_PG^P6JKamUK_+|A^xfv8nS{ zCti%sB=_sZ3vzhugLuL4wLDI(4^@$sAZ97>7s?2z(JTSdC=q-{k#Yi(* zUuB@g`AFC^>uMlx&qv~3KN9!KEM}1XTl$|G*oNB0v;k))Jh+o=J>rwC$Bg{yT{7Fc zUbyP)?9*8cVnQ!&M{VTW^at8y8Z+SjGgvJbt&1C}cUa{l_w1RIjR9$si!ZYVbkG~u z$7QDAmG?ega6HaG@O<~Vk)5-8*IO6x^?vzeMlZ2J^b4q12x8kW-=1jcvF_!78f zMeOPS{r>cy;XK)X2;yz1-?NJB_{-}iQfyoWm%mY$6E*?fg#^o^O_=hXPW(HrsghZn zAZjmmcV_>n09%PBbFLt=#OwD82CZf4QJtZI-+vjKP$B(LO6q!Bp_=y%tT$3}7^?SG zXK&H$WWqB2zPs+$jZ#VLfo`9SQ@FSt&w+Cba9*ElPA=9{c))Wepvv%^mZRFubAEgl zGntc4RqmSRnARbrIi_`3KyyyXp-f+{7vZ;X@%D|jhI8IYdAb`6LnB9@ZA3$!wA?h* z%xz{nd}vlTHnI*bR_GcC*`jQ>AJ*ZF?Sa`b(R?#!K zZkp??g5!>aGvbpZM~u_2H!`=W*Y9FIu!;Hv5A_NCK)V{EB_hsin6i4V?d>17`Ls;Z zw*iv?O}|y3mNf`Imz#M(dr;u4Bj%uRF*JRfjlluL!zz!Z9{y|kCqP<#C*UR?QU%;;paKWpBfIq}ns4ifbb20oP?eo1J&+O(dA4bK!G{o8L+NB>h4t&-*DL%RR_Xpu6!SsO@- zE?c1n8&_-N8nt~X0L>BZHbZQkP6EVoh%H8BO3|0*UV}kmXAmB$LsY`@Kf>44biezS zWPg!p-3YxvRTFpI1?yz=w*ZoZ%nj^Tu&uwQ805! zgcoLr0^#SuK|pwq5H2CIwAOJOCdqRByhXiZC-~;ZQoA5L9Z$4;^I4DSTyn`IKCggA z5d7?f(-#E7uZM6Ej3u6Tdb>_dPP9|`=5d(bmdD3zH`QG38xq%1RgH80bqS*;buq^z zO)Cqh?_xun3D<`bE=wi#2Dsgaj-#-Wrw?|~ zy4$T23l1Ya$7BI-vhJ(tJC2vZN?X+UD|t!%x50M4Rr7|eB?aR;iGR-1!N{JUGi+R~ zjjIw5RBNA{H#u8eU#l@pdrY1XI4mMe0?fa@2I<95nS*(AtgAooS>?Kbbzt2kSvQ`Y zSvR>l>na$NJBY_7{>r$+@5=RavY&sGN8R;McEH4dpI1ErpK({An4EU+!}SiV1MAKw zl+l@W9+~P5)`4}sWgTT$jqo>kMN0(~610Wmn6D(DecG>CO5hA>F*3#WNq$L>0Nfpj39Cg}>?l63@+ zR^QW`xx%1gG%}RRj=5)NmUq%zZ^e@`J=6CYhiGMJ89GZ;l)8rJMf25i@Sja8C&>}1 zqREZLzt+A=b&bJ-Un8GHyt8(Qe#ue&U zPM%3CacHw&rgjGTf2ObY&VTohP0GVCQ6M{9V~m2Y%oS$YGLf_8lB3|AksJ_?>W}Gd zN%r&)I~0*uLi+Ui-RA#Fum4}^7Y5^l4KrHvfKmapKbX~X{elOS1Ebk2D&9F{dT!J^ zhpLPgu(B78WzksXh8ZmYuLRM5hfnF7`!d*nAD-by!;fyC^(t*K*b`6MnPPfS+6hXF z*eeyX7t3v;-%RlI717>kmF+b6rq~R{TD~Ft4*=8v;A{ka-y7#PFZ~xWduZ&}8+ked z>F@-?VF6?kA9DjHSo$-WEmfpJmkiU0!-O02Sz7tjZ^*}ne>Uhnxn>_lG|1E8hm&Srd{YVk4eMT z8b;PadXRn1qjh>KTa$G{W!4HRJUH^@xFxLm zQ=(gp*$}Hwy4+4>hcWsi1640bRS_TSsjBCz-ruun1m0cl`dH1>`}=00Bi~l2j{Lmo ztci2+m_B_=SRKtKgsZ;K1~&khKxe;4vuRFNe?+u|0$K~dHn8=AY;EqXYKpQGRs$eb z+G?y$nn(bik#^YEa#fpAmA3g5xT@sKaZ5}+_@5_Nm91_hjV8S*VQUYm_bqvrd4OwhrJQZ5toQGu)cj(@1Gumcqvqcka+A1g|9l74u=YTnJI@os+ zVAjmB%1-wB2>`oo61_g|lXs17W+0#7{+^?i(%^3kq=W<2lsRN#y69E#GWPOJ2sOM+ zIlj!CcF5$G?2trpw2m`&sLJRex1A5E? z>%Dvy+$_(F@5OmQhSz(Nw&Xvsn7+@`mPVe|A;W8wTX&Z$r?#Ck{D)kzHc}n|H0z>y z|7RFVI4N;%4_CLwDQzGNnhI%3Z&4Md5ih!RVE7Nw(UaXX+?3#AYUAP_VZ2t1OkIWjwqteHSy!oBRbK#*FPW_~D} zH#c%x?7kp6Ic0#dGyOqEyLfIyANUq7{_%Og*zj)u2)_l{*Py+he2+A)=D_LZEv zmAq+$+b%}d`-g^PeUR^(J_&EyJXTdb>unf+U7B#LM{nPK%if@gojx2WrM=T*w2I>N zMy;R2k4g5&eO?;)q));8%ZGTmPO95~Z=GB>u4ySTs>k$od#8;NF*VaVZQRV-oa^Fk zbI<>O_TFy0Z6w*&eV6qB7XE+sUE8i+U1OB1ecE-Z&n+#XWR0RlZHlUHzx@FTi3pHD zWCk(`WPFC)vP|gNNLT}6xoA0ixL#>CaBp=~#E4W#NqX^H)7!1( z=6GwkIimfh7tJ@CXdQSa__}(op6cJWvI%BBL@jn~zeNw2kxpd^%%Yq*JC6Z#P3o1D zq-x!S70aV5O|%S2%v>1o`v}{ZxiGv?qNORAwa_|bAK7jmcUDT~4%uRmO{JE{?A*zUo zN-Wvu$qVb~HbyJm*3si!n2EuXH;0ZsDS36K!XK;q)&5rr^ExROctEv0CfkMQ9*+QUnX|Itb{O`n7BD8GHtx!DsN< zGM{znI)LZ5w-|f|pTTGF8GN?NX8~RZb2~n4tNOM7TW!8qyX_B)KD*n)!SCNXk0;+@ zyrUyyt#+AX^CM#kl*zKyGsav5)I0EHY8i^lM8(GH6{xN9~(6_5oUS+U@PMBlI zqkjOYCXzTIztL=UJi0hR{k5PuCz)4K zNnd&%AlPPJ#nchpMRS6*G*8g1;43V57yi>w-*Eh}+{HorM9ptbW=S-7-h@2I$L&WP-&gC+e}38AZ}vBk{n?|7@|dm3 z)7C@lL`~jvLyJ-D*qKg@Whtn~aBkloTk=G>w}u~A=}hJ=58=bbLNiA7wpN^|K|OQh zq&(SwUgx|ov?RXig84=T*1@6i`jmg~uQ39#Urogx5TjvZ3B=dZc+DvMw1!s{AV%j^ z+Ie*s#Jxmi2>fb5dEBk)f03;bi9^07F5E|Ph@#jMi$hUW?6H`i6z!v)4Kcs-CwNUX z&PZ7t24BZOkS%XAALkH1*J=3#{j=`%)2Va(3C?)joj7 zv;#obbNAGJA*uVwA7u9d4%zXpv<-)8%u-cJ;wzJjA%*fJmP?_<@Ud4+Vgo6(m&AB{ z^NPg!TJi0ELgrIdGxqjl^;~}pp7wth@AscBGs&V*tWrI;ti9%PSje`XdJE>LlqP5@ zp2mYK4L2@{HX0|aHd?mY=<8~o9x``6Xh5^GXx7Cn9CjD}15w|1yl~h>WU@Po=X2Nv zSFjwWE}&(r7TJmL(=Ow(?tE_YS0~u+yPYbydw0$-l%&e!=)6aLj|#^YmJ^Q(Lm(|k zIC>ni;pjyhjvhAx#^~pdOg44t^YceWp=tX3Q7$^d-%SW>0ah2z&*QBXT|$>2w35&z zOK7UVyG-bkOdQ84J8@jL6UT9Gl(`?4S3>Ccq^vboh(b)F%#Y|n%noq+|876jCmA9+ zM&DP7WcnWU70pKzuG2`)$j0y}V;6>J?Y{6RH(D3|I-IDDWN)MF;!+sKuS7DWi^-AP zOVq(B!N2c!r&yfryM1}yZN3}(<-(9{2j)v@2Zl1~)LXJ7x!uR{&I+vMk!AP>m}cY# z$jkU?-rfLHa%i`93egU9upM}U<(omQa_a{7FCSl3r?Bv-it3d05@w#%)DiW5rL6ah zrgSu80%Pxdsm!40tyMgUg{uW=vRzL$E1~uBZhMZxw-2TqcSp`rcj%jgzAF0WEPZKk zzsvNEDSZo;z6qgkX++;#Mqe9i2xp;h8Vc>BZ%i}mBMD1z%l}8@O%@&aHO+U%9l)y?z-CRvSyF=O`fIJhB$4pcoE*v$&*Oexq zSt+FnRzFW2)6WN`38Ho?VnL%;?F=&`Q9hNW7ag6x-mzF3n31q+W9aZTNIWHp^{4kR z6@sad*Z;PqSqiV30y|yCf_f0RlWGi(PlcS6s!HC*!>4v(4BSqHTb;3x8X%j;yMJx| zV_xSbsB=|yZlb7j-3nG3taE7qV$5PVBEU{klR7tW?j&J4caZ|xwVf!LqmTddaR` zPsnh77&?RDA%YHt107syzdt-b9H{iejJxa%S?-v*YBWyKZrSX;ugAzY35J;J5i}TpL;)LJZR>tq$}(a(_Byh$Sf;hn<5a1ZV`%2)Y_UKpb4*oEpLMgV?iBs1dLe4ml6>otJ;u|3%)9#%->c zfN-%6>?+lmfUtwxTs8sWuP7^TeKF@QeS6R_ZDSIq^t$?Xh_-PXrgTU{a)`EP_*5|W zOZ`!MxP2bKqMm*5PTVhVv5&(NZ$*nI!nQ>8jki%ET$mZ=!^d|2{;mnPAu)7!?84`` z4O>HZxQ|Y91Gneog!A^|a8lh%{OGFDXmUO&?803&8#xJXch#a$I3En>?Mz%9?yCW& zfGI2r4!{%^1(#u}7x=Y(SgmX9)dlRk*E0;VZLyF^9d65ShYJJ8!XbwE7}0HB1;5QJ zBI~@0)JRoGgDduLM$y*Rn`RH)h=EY=p3$C?|$37-)jaBkN3u&1bUb`zY}Pm zN@p3XZ>PS%n-93~^HfSLo~ zU2j%*uOv0oY4`t%pI6)Z8;?hI6rsDOVs+;|>RaUNhd%on(%aCwGtAsW>#(h$lh7z0 zEQ(~!HE$)_IFNdF6(ea8^1&<+`wh1@y|^6O%Ui$pUz^R&paw?UOIZVZIM!j+V~#lp z6;tJN5asZ(OP_<7g{UW#M4M|OkoqpsVmtxk31DcYCDp#Y*j$+nV@&=lv%q6E-l1@E z!R0v_Gcsx(UlG5FzllFioVp%asrNSY~4rs|+#+eFXb!e%}8n zt=3lbSrok}db>q06#)3H<`*t{ZE)A|qF1UMpy-{1&IGX9wJ*8GyaX;&xD!+*aOXYh zohLgkadEl^PI78cNJ`eBkmSUo@H$a9I)*Wom=pqkgTZPrS`E8RN|5#fq*YUN0KYHW z^-urZZR_84W8g9fGcZ@Zf{bOZH!H} zb`IlQv>kxvpzu(CtLOTMhTz~FpnVq{FbLu9Fd6Gqz%ZE&Fu#ITOK5o6J=A|jrU>5u zGp)AO@69(JpD@lkPBX{G14gH3%NXMALvkuk&yLtQ*N)9>d2|@Q55*)NNU=^nZqtFn zk{sI2!H4;szIi6#pBrB(M$T}7$4(7 zU}_rcG#j^PY#uGZWT+!m0!*gpruq>y5tFJV%vcD=L=ex7(t9!)p1s|SQI~|$@E(@C zwB-oyPQqP|se?NB>See2`@`--{mttu>hJ$}Z!X$&4>i>~a#lX}VcR@f1eUhV%hYh6 z{W7u=SS56Xi>`v1l7aSY3btpHkenAf=N0GIqV|3dZu36^lj%Y_qbRM>P()J(qPrs`kn`_L6Y`Vvd zESWp_E^s$j=`02NSdq6+*iJlF452uocrVy8Flo!csJRi0eO4)C&no`n6^gfVPb#Z;$Bu|5eF2s|l)Ayz=F7$;WHAk$<{pM?#Jv)f`t5(+_^4WIhv!USh8%~_pEl!xbE z>m&ZYzpT1@_w?{&k~WPloHA{i?ZS~yjnhSK)Lq=lp*JFHTQj4rt)iu^9(%V_5`wD7 zS{AkiWQA#9%Yk%IC*J?=>-~6&Ev@+UKpT27-9rhk_i~-SMSVUuKjsOW!NAw(-H?#E z$LCfaxgJHC3D?U^z;&Mkq7!k59jh0I5y*ZivY%4)>h z8bd^ehz!S@<07)y@y6;JaJ)^&TRqPk;0m~+LpPrr%_dyogt!!3b((VDSKB`u{r6v+ z{~*K@Tt?CBE0^jDgxm|GaE@!KtrB}*+=>cRg1&A)^~4L z8|=7-Y@yt7t&lB_MkJ>z=B7q_+i^{PrJgv6q@{L~0XjOeUnX1lyW`q&oJ1HvxR{#1 z8f-mrPcREd>Z`YZJ=_{d8DS1GtH|7WkIvCtcp_#LL{W}oR#7fmMLAB46y-3Njcy$8 za=x)Svc2*;!05)M*sP}LKw*4x;fK}s4swQb?Dk#GfF*894O+GwgzW=D&X@|>d-CdS zQC&UnKG)~}^N;=L_2<>@n_+-Oh*UOZz09Vv#h#?Hl`WA=(#-7X{1IFSrBSgg$z+TC zjm1o*s|Q2YiA)1ifZdB@SB+Ht>e+@RE+G#}AaXB}>08vd%G#~XtwThPLRuuVp5%yh z1e-nHqgPo^Uoy`%>MV>DVqM4u7A#H4WL>lh$ZTqoiyQ&lV@fAgXO zhOk)XZE$GyU5d(D3>~P7xBGmH5B0y*pVi%m`ixJjr~OeD>~ImbD=7y^*zHPjo0I4? zg+tQUE7_&cVY?JM42_mTM}Yvsq|V@ z80~?6E%mNou}35fl2_Hp@uF+w*-@*&D5Fw5SGy12HqX`Lr>chQE#fZRqqpz%G2$+! z%cBp4_KS4*RJbp!Li-f7n&t87WyUpf3Hz~f^BrqH&OPbnHKTa16>`9Gx2cL>H{Thl z$fboP6k5vE=QF}-9jpI=%YzDFm^CB#8x*)_4B)-uk zDY|!1C$!WFbnno;18fIitChZaxZ)qx`bp?S>z1++f-01?Y{Vv26rLniWC;-_=!0}m zIh(e^M4lKGw2>sNB51?We5+{F+;Zqr z?oQi+%jvp1B6W9|uyuFT*4<%hRCgzmvODxwNUqCnS)OvnM{`0+o{Q1O{2g@LJ+J<% zKHPrU|8&-80l&0oj~)l~aS0w49bRG>@M?DYrEMyyg}9<@(aD~}m4(6H$w_Yo1i)t# zfc*eP8xd-VmKuUK;&j`vQ(>visv)ONzGX3cHoWR69pv@KDnHuVmR;!lH2rwzAx0)rZ%vLsth^3M&h8}P&U;!q5%6zlo`wc zyP%|OKX?v3AoPVW$1R#;y3D(8nO|}wcU*=hHTCO^xVA&u+tpEVAav6y13=-Bo&>@& zzzz!`)$W(g>aH%Me}25%|5I+Zutz%wcHc{C*rQ>OzIvLUlUA>iKEDo|q_9Z}o1|t( zZCVKPGV*x%*!qzS^2H@ttHsg5I{Bskm2O|2_p4733+f)ZQ6Qs0)}(eGh-bn%Y8S9| z*rGONtX64EFEFl&X{!fh?WGxgi~4q6`(c_3Nq}4!vI%mu6&M+1$TgYoleYfmGR?{S z$Pv3;mF`{zUG348C+Bb#{B&0zxt|04+z`0O(CL<~s9%0vuSDo&kT9NQxm)4A1{om-8_+-j3)F**@LCKY>Yvq%f6(Tz{U zfK+@z(&|uDQ6lf@`lZ_K9v<&eBD+U#l*lNN;ip&vxG87_B{IFteA8+G=~x-p9I)DP z?&Q2;&+ZXI;fPvYdp50xM@C1@@b&opBNFl0<4vnsAQX>PX5<98y&!INP`EvD_^t< zJX{)tWW}W7p)71yOdghj&kpiK?!F#P2Y`OO__vz2#t=?f2&Zj@<0^zxK-dn1QyGN) zjZBuVgUZcbuc3G0GH!B1r@T99yqwJMG>yQ`Zs zKdqiVY}OkK;q$1HQ6+b)WSE8BH4C#%JeY+ollw?nfm8T+jj>HQM|G^Kj&sFUCKs2k z_Gp*1I_7nWm{iB5nZhn(vA+ttbPI?wu$cVKfwER9t0bsmithJ^=ZCNB>bK3)FRQPW z1;g-twfptu``2pw&p(cb8pO~egB+!TL zt7|fWEx-g2t%!4aYDIy_o6)HuEtx6Y0t}afQ zR^D^9u3u}TiC4dYFW+C*`?vbzYPYg|-n+M-bo}<|h_x*jj%}A4oxBi9e9M6Y58>Tl zk_fKXzL$@MdT3dCju%b!;nE``QG-#+Mh#|d)L@hq6WuC~Aip^Q=js3M1L;7yzW*kl zR!>dhvN3;o{Kk$o%0^3|vZ)bob8nJD7{eQ999PmsyK9HB64C_IfW=Bk5=?1~Q@6i^ zyXWLHBg5!b0dz=rzWZ&nzh)eByU98w{vK2tLdaXxXK!}vv0xA+B>@SgK##pSXhG8a z@}uL13enB(m_y~;%`Q%bH@k@4ng=oZg3RUMi9X*%eD%H0mk>)tHfNvD#zctQeZJ%* z7hcI$eo7CMBNs%g&Uju`1M}930=yr!{&YOQKB;b;*^-3Lpd7wdrrKBzk z{9REI2f`H*P4J>4(ec&1!KsI(KL*E14|_z8LVpfCmOVOktZ!hobobk8IJ2 zc<#`aCn<~HU+e!d(&W&6@PQXuxmStxB9o7b)BGh?k^ zqtLw2O@ZDC(K|@d0ro!b4HCu@?}>pFnH5R`M{H?5jFbe zcz?A-nQ*~hJ+?8IwC+SZ;yP>(lb~1!sslW3&adtc;TNB5>Jl+@38fKDp-cFYv)Ncv z-;8Jkt3A3z+3FIFJ$SScO{PnDYeY*zA(QDpz_q!q-6667t3zO}e}MNtJ{^l}gZdaC zR+h&sh$BxxOgLP!7$y`Bj3^vRC>;4aDIJmg4J`_@+m|AQxlIG%VT2B5x@|X`-Tzzv zygzIx!vHV93-B71zopdgR}3EuZTO<(Wzjj5542CFH&2eZyZh;Aa(n*!X8TLE`m2(K zX43aEQS85n9i&p{&XYh9x3ga~k(tu`^;JvrEn~h1ccrfCi1a5Eae)yrQtsnGXNLFd zVrL~t2RMFT?LJoP-D(e51IZY(>`KX)&D2O7%hVY9oP>07X2>qi49WVuI5uH0wyv~OH;Py~lB=$pal#QVWUz)PR^Kjk9+iJc3wc39! zSKA-|s_Kij`$PWrSN$6;d_5V>xqI$F6T8w5+19KXumdhYRvN#y%`5u)Y_=EwQ6lFigtHV zG#}KqXtFy7VXfUvcDxtc>f?|4|9`msQtfsRkN3|l=f~F3KF&p8_T>_FG{!szP#g2= z@uJ5(hVSYIqezYc{MC=41=j(-*B8GUIn0S1vN4)*TPL_m3YL?$t#r6Ogt<#ABD%VP$q&@iB3XlWQ&DvhPm4pQ7ndw*@ zC5-N2bPuC@M&wNs;movRbPqOUd+1fb5``=ZS*=1ATa%{4Y2LcbE$9&?GAQifRHxha z@O&8Y__Dv$MuCb6UuA*HPH{(0PI31{UbeU`Si3_`3RE+NSAt&hHu_eeo<7X0iOPD3 zp$*t1s)CjSkgZAheYJg!{4w`7px{Np3*~k$_gN%Nh0lght?!+S?!Qe;=PqWo%HPf< zwpMQ>7*|jg!SpTai!yT2xY3iVLKz&f?)a#6$A?XN&*3l!YcRA3EiD3>nA7pK6i!<3yE{-PUka}}S_Hjf&t5ByI+U@> zJ{?=el9JBpGB#}iT#&xwIdfYZTbY}bu%V=cjjW7m%;k}gnbCN}!i;t)#A@MF1HD$T zi_94%c0J4$vKVrL7#RZ@EmYa7kgkj6!+eD^|rcu zxvj=;w2RobSloW-TxD^D)~6j9N3u@e@fF_DIF;tUvx;-JXvKLL$4#f>Rh?t~lp*m` zhLf$cGa&LgQzb+;UNY*VQvq(2i>%_1A=%#oITiK*`5@s%+lPmVJklHFJdTlH43qNd zJyLmFf=PL5h!>G?nvaEe7*P^0j^Zvsms`MbUhDq0dHQAbwX(5T^z=mN%D4wXSB9<( zU0F@=BQ(#yB3G2#OHJkEkWGCcfv>J2I7G6CCGA2IVnIqr4^3nszM&rbqaL@o z)s&9gGmItPFk(mB3MU7OyOnK(`9({mZ6q1j3);w0-)h^0j2c>rj}Nc3yv^eF?jEJ& zbJ$bhwwrIGeR}g;%w^d*Bq)%Jj19v4ev7jG%XAgE5w@6Evk1%fb@&@-DGjAi0e9IG z(!;4NasVqPC~(Wh`2y4uPFz39q(WhitN)P-jXg)W$@&0KiN z;3Ye~BPwq~@RGqx=J0*Z0mSOL$f~>h>K|48o9>LI-}b2M8aYNEN+m*Io_4N2RBt(q zMzRCda7)V@7Mug6FuCTsri(E-tHdoPXXl%o?bjbL{_ZSswH04-4Fq33tl+SU)^{-K@5E#*!}x4MKy^ zz7U#MdVuJQAi7IjhSOvT-tD%k|B-88{I)q>`P1Q`yHTw*sQ;qvqh56%hX^{lVT5)j zFZQ6F=^Jx}JRL*jF+DgeT$5qh$EA8UH+*3Uos!L1Lboh9Yd7cEQ%1GxFtj71vtN^g zLv$WL>V;WE1)DSB_;aG?`;W7+>$vAo z6RpHsApp%syA+8TT8ah|GsaYQNX$TD#uZ^$ zUt>PI6fqil9F{cz{jG%&qk$L=P{ZGR3W)bo}k% zuD)uud;e0uV1JUabbNLqhQBfVjp6Tkr6S1nZsYq5n&)59myXXbH6HYavZWp+^h#bI zV(@r96oS2y!Vv5ifMIRv`211}Bi+!plpOX-ULWG{Xh9Tuy#m9~>z6=bNXL+lPstDI z_>Agv(-zXPkGR#7jsaGH6=0nStVz=Tl3HMmhfl2^B8YYcu3F-%PH6JOV}1VaZ`*2x zRD0kUIQA9CsB?Yb@f&M}Xoi6S1_l@yW~3_&41U3t0jCL-U2^3(a?4 z2?Cn0SAb>Vse@c7zfgXm{LYSDD8JrvSE&BJUIqOZ`Y-g~nYj!7*I(?4G~nl-?(VBk zRZY~}-yfbIz8(k*DcCJ?LkflztV!-Hwn^@i+*LttTjV}xuAU%#ex124j9MABGHPW* zjY77}FB?9!J`Kd3h`3rb6`jWrLb5gDmO~>-yZKiY}T7?bJ8#MZ&%$xU3L!WP?w=D zUrJr({TPs!FGBQT`fBRTXV?95T%2Z$LDA+ zTZRXjQEi=xYU`s|;9YG^`KN{AX!uy@&NHBR1ZGDQ+gzLmv6bR9OKkG+>NK&#n3UE5 z`IUOq>7<2rqs)(1rzOx1-xa<>JIST$bUu7+4Q=vY0c|&+EfB3$M_G`tL2cC8sI!gg z>`Z9yh}ec=c2c$j+4Cb?rfvVSS?zP_Ul9y3+by(1%!Zha)Qkm@o#0key(U*6Hv_pD z$jz9udPZ)BXNXncQ;WKY&OmeqqBCYEFQPL%Mz26erkBC?4BIno&oiSJwr8KoJ4z1z zw0eRX>>RYA215;o8oWGe*R++1!Sw~uf}sUN3!YbzfEMf%V0n1zAQwU~gkT84Gh;Uv z3c=;@sR+cF7`t*6cz+c{V2HpFfoH}pL|}ihE0cgf*Pqh*kN5BkeX74Lgka~u4Ivmp zFofU*k^7XnnsV^zWe|lS3PTj0vrL94>?3S-K&lU6$i$F|ArsFIV)%s@L1KYse0D9A zV<^W^j%Ow@l;dT9Sf(O>saD(DZ=Y9>)%x?p<0GPy9i%v9WXQ;ul9Bm(8anc2IDST5 zO(FU0I;`SD<&4UCb`1lo_&g-87EJ{xHcvYy|MWm{FN*0~)VGSy4?VfLp}eIXlMi)I z+Hus@&W)HpI_+3iD<2a`y~D9o$e=xnp6E;{8T0X2AS$0<3{@GmF=}JaU<_5+RTj&1 z<$r&B*j4XWw}0MmH#Os+DmzDUsLD{4FQqE83I{~x%V7Kjxtfyl$yKl>!Tau6XIeyEwMh-F~{$?Wn zhTifwk=l-<;bZIL2%Z7pi2$sv7N1@Str!I`3gDRm46Cv4{FUg$-&ao`HtYQd849s$ z)P_O~g;>9A4k>4q)Mh(;Inj?nZA;WXl2%I^KEB$5C=4GPd~EQs8RAvwW8+l`_}DJV zSB{-Z+!~9(DR8U8ZOY=7d^nuOZA5q*CM<9xLfp)VxT%D=VLJoi8*!7Ot=hGS8>6Ci zfweK4!?}vY2=)JD-8hS7E-b*=xvFC0E zV0VD4Df@P=L5u`qBoHGpXI+dK3EwcQMyMux5g>ua8yau3^A`aUoQ33o4qFbrWB z!tl%hh8@^<{z^pR{T964hx-5Du73XG*Xlp}6MlaFWwW}&N;?NZ4#gOX@ud`FthAdR zAtui&W?sz+X`}@qEeL5rMxf4wX>Ad6 zYz=B?8ZLoaB3wV${~YzVgGTKf&!JI6qlT+_F*tt(t(F@7`dZ`~!^Z(1$IO*7a*cf< z>o7GHm_>du6bUF2Gjmp)UmOn~i~QnAK&veXzg+-hG74H0v@?Si#$?|(E7Ei8qo3;c z=lN6peW2<(M{20LP<1b*>SBA{^r)t|)zfzA6;OCl5ThWTxgdtZ>lbBJ1l0mBG+$`G z(0pemFLu^>jb45Iw{sa(U=+P5dS^y2RA8UUD^r9w|M^taM7{lG^|f06T7UKT)%peE zuzLW9I1F(Zmfr;tyq~<9I&uHSNTWa+1=1+yRV|Q4;VF0pP?cgC2^2`6Kmx_=a7F^f z0_iLeng>pR<_ygln)B>zhUUC9G@B^V_0O|E2tu@TREH1^AsRyTVzK@T-cdUA>$Q-f zAwxrkp0k>U4DBm+^^q!MGBjvt(9ob~Co?qY#i4n$0{wOX1ZW7*5TIuVGX&^maak%q z|MPME*++8>6{Ip zdPs-{b*G`OE>T55Hdmp^9{_;d3uO8h^{LRukE7E-Zf+~Db#g#H+C4IK)K<}9Y69g* zRy7|}v%Q0|ROD#h)*&iqP&wwMvcCA-dpUGx)XAumJwq~dXP1erR+}l7_ur+VhpEPV zJZV&^P2cA@<#?66D;(ri=bl7=0o;=e5F96>s`!X8-w>RnZAGtz;Fy(&vqc-|m?yWK zJh@T-O3#y9(LTAw@#J7Xd^m16WDbO{@d6F`CeU zr@*X42(C}JBm>tc8H>Ouix*WE$E|{7q4B@yI7pPm*H{+Uuf5^u$x0FXONfgJx-#~0 zr3KcOTw*rvT2^$_cQK)7?}}F@4d2nnZ;_ds-3MUaXt9)2CYslx{RU+M?KdbBo|FfU zu$SYfnz`t}X)zZaxPf{u9zGTwICS8yfV#3CTyqVYa42;VF@sVU&>aa~wBgW(JH03B zw&7AT`I^t!hO-gm?sgk4rMDBE8(TSiYEAV;KTf#8Se;dK_2;zO5skO{!YvgRbpE;H~Re zt0d{56Jr099H?6VMwa5x&W}VYnhs!)iq_8;q@p1e?K(7yb8_~4NxwbUb1kCMbV!V- zw82u3Wig`C5S4aqG}dn0^<0bgogR+SzU#*^+IMK*0meK~(ayW@ABFlB<3~FWQ+4j9 zsbQ;zE6~-OuRA%&SpUt~jqsUc6LDkf+)Ty^Z)JzL5GueK+hEG_C4c;f?=*2p?q2jQ z>gj?;?Y%800z;>)p&v5THM4S8iO(U8;p9S7xwl}so3{w^n!AOJyT@1f#@#4?SBSWq z@>3aC%H4!=w`94SwA_t?A?_C7uA|)5*LF`XG!}MYcvz6ED>Xdu*#^0Q*ZTO+tKB#A z5cijD{gvI{n`2QQqdqoD5Y9P_AVC=Q@p9_p6B6I3Njgv{pByx*6$jx;mImaPdOie} z@p32hc>czhlM~3bg}Pwxt2Xz^X;8>g-hhzS`bDJQ{b^fMehoI7XQZ9y2^{SS!FX{0ZOK ztbMLl+uLuSYm`=72)pjF8p1Aw-Af6(+3=|lb}t9(4sx~h-Od$|c_H&c=ABo4fXwR~ zW>pB)BCmO38MI$zVi~Lb#_iy~)9ei+QUeDB06mFiaa$pWg$aKniToSBE4>A9+Wzc% z@-M4OWRQQSC$P2{+`SB%Ff?Ik!n5O7rU|ook=g19( z7z**F6k;~_3<~kZ&`pu6rx4RCENr(xk&Gf4MY2heEZyqF8z&aM)yWysswk=jUvq;n z)>q0k?e>JY z#G$YC{grHC#*I#WqXXBWY1rbr^|E8a8wKV0O&>aYzj)5mfqK37;xVd8$2LaZ%0Ane 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 literal 2317 zcmV+o3G((IiwFP!000000Oed?Z`(K$|0;x^xj})X~YSuanH^KR9zA=!#EX zO(st#N+;Y-Uj6=cqC%U|+CjU!%kbBwmI*iTn>uI2Ja@?a_1n{l6zV4Qz)~R-!xeP$ z<=YqF%*1DItkBRhIeYd3Sa`DZgM6v=^9cqh`r5W#aLbIyoHj_ASgoj;QO0=Ycrp{# z=Khh16Hemnel1AkZ6H0JBt;Kl^etVK^i(MKPH#R_nHK`UmN#OV)|v`zsdJfqb$Xg; zW}o@FFsbw8nb4<+ST2QHo+O-S+?=jB0ATQIA(X(e?J3Sh7!b9e$;lbO3~=UBvm0(^ zYoR6eymdsc1C@NPs7+9xw4;(RohwK5qJW0qT*`^%Yi@+QBgHUrK+cytAV1`*Ibeh- zzeY+EU`2)pV_G^UskJkvSDECuAmlox=^c}Tktwo}X~oh6_nzL0RlXt&`1J~wn31YO zGN%@_RV$KFms(g+qw{4-DDgv+`5k%BrgYBZ5eM&C*=a%SU)m^8_YA6t`%t z1?4FLK-?@S!|z<>D@&+K;G@6G(8mqp_bkVrXRbV5-{ z;F&oMzaZw^^$#T(t$gaq193pby-5X2aa)uLnWJsMlOPxXpDWxvAUud=Yg7wiR-H-7 zw50WACEWo$_{QK5%BNwAbdfAKWE6r?*^{$O3-aTWmU5sdP$zi?Zk%G~>657)%okfC zc^!!T3SM;-vNF335Msl*5R&)7y!5nwl>S-J;xpqf5RGD`$=Trb1*_Y+ z+5EdCjqWwuPNGVan*bGb$GQQ2sflR6kgx zeFw1E&4Iz1hTYz4*J+rpOQ|a>_XNI5PA%Yv42h-GfHQ`tg0c%s)V8hY0B_fJ26yX zw7r1UM*2b+P#h;#ye#g)l!CJaEJ7#oh^hC0-iv7XkRCxrc2JL)dQN2#&|^+!k9Uzr ziuOS;_bJ>%r>A+A_;~#oi}+qB!=-I6ls(GZqb{Nx=Xlj80Vq;u{$!YG>zZP^sOX?M z)^*eoD+cVKtwNFHcOLCf@!!M!E(ZX(=*J*~Rt2LWg2$E2k4g$%rSvXoJfxl;wIBx4 zf5bml0vL6X*#Ggm$F_y+#86`fQeXA>DqdTFy^+J-MT1_T#}zjW6%kM#qlJV~bMPY^ zuRRPE9=w3YiVx!g@;w}_fGlS@;+Un{*$h5S30)TqcXeR1-u6!%`_3CX4M?r!I2whVl>0Cy#E&)ldpxZqB%cTd_Z#9-q zj5`eRAvGLLqWk!4t6v`rrU#i^IYLBcH<-QLCv#~9n{mP$N=<%bS`w)=hVqXem3zb? ziW`w-par2C)oJPl3;9$Ota_mqe34bRhSI3NCWSh*7|_Y$Zqu{A*A;zWaqCQnUsP1D z88@bQu|gXZDl7R{VCY9xscN_-lhQSW{;(PIIO2N`6X%ngzKz%ey)0QC1U1~wqzZWW zZ3feTCVq<-V6c-I@!f43YzGPJE+{*ifn6!3cT_3fPReSr_JwMiLVR3s1wa`Fo2 z-kEuXS^3;h#nNzgMwfi&l`ozY4$hD?a9+H~rM#<7#7nfriR+;&#Cu7HUYOvE!)d+jdbX(Iyh5%s3L=@A~C-}zK9t92TK zB>F2s`;~y&6ybf!?eByETHM zH}DEYw&maOCFP+jBDL1B`&VK&q n%a_m3U(oZjUwE=OJ3l{rPU(yD#fx9~ Date: Wed, 14 Aug 2024 13:24:42 +0200 Subject: [PATCH 03/40] chore: update password flow --- script/run_analysis.sh | 2 +- script/sonarqube_management.sh | 47 ++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/script/run_analysis.sh b/script/run_analysis.sh index 0a774dbf67..7fd2b76a33 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -24,7 +24,7 @@ OUTPUT_PATH="$(pwd)/output" # Directory for the output NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" -RUN_CLEANUP=true # Set to false to skip cleanup +RUN_CLEANUP=false # Set to false to skip cleanup RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner WAIT_TIME=60 # Time in seconds to wait after running SonarScanner diff --git a/script/sonarqube_management.sh b/script/sonarqube_management.sh index 53e940d413..cf857468b8 100644 --- a/script/sonarqube_management.sh +++ b/script/sonarqube_management.sh @@ -45,22 +45,53 @@ ensure_sonarqube_running() { echo "โณ Waiting for SonarQube to be ready..." sleep 60 # Adjust this sleep time as needed to allow SonarQube to fully start } -# Step 2: Reset the password if needed + reset_sonarqube_password() { + echo "๐Ÿ” Testing SonarQube credentials: Username='$DEFAULT_SONAR_USER', Password='$DEFAULT_SONAR_PASSWORD'" + # Attempt to log in with the default password to check if it's still active - response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s "$HOST_SONAR_URL/api/authentication/validate") + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") + http_status="${response: -3}" is_valid=$(echo "$response" | jq -r '.valid') - if [ "$is_valid" == "false" ]; then - # The default password is still active, so we need to change it - echo "๐Ÿ”„ Changing default SonarQube password..." + if [ "$http_status" == "200" ] && [ "$is_valid" == "true" ]; then + echo "โœ… Default credentials are valid. Proceeding to change the password..." change_default_password + else + echo "โ„น๏ธ Default credentials are not in use. Checking the new password..." + + # Attempt to log in with the new password + response=$(curl -u $DEFAULT_SONAR_USER:$NEW_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") + http_status="${response: -3}" + is_valid=$(echo "$response" | jq -r '.valid') + + if [ "$http_status" == "200" ] && [ "$is_valid" == "true" ]; then + echo "โœ… New password is valid. Proceeding with it." + SONAR_USER=$DEFAULT_SONAR_USER + SONAR_PASSWORD=$NEW_SONAR_PASSWORD + else + echo "โŒ The new password is invalid. Please update the NEW_SONAR_PASSWORD in the script." + exit 1 + fi + fi +} + +# Function to change the default password to a new password +change_default_password() { + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X POST -s -w "%{http_code}" \ + -d "login=$DEFAULT_SONAR_USER&previousPassword=$DEFAULT_SONAR_PASSWORD&password=$NEW_SONAR_PASSWORD" \ + "$HOST_SONAR_URL/api/users/change_password") + + http_status="${response: -3}" + + if [ "$http_status" == "200" ] || [ "$http_status" == "204" ]; then + echo "โœ… Password has been successfully changed to the new password." SONAR_USER=$DEFAULT_SONAR_USER SONAR_PASSWORD=$NEW_SONAR_PASSWORD else - echo "โ„น๏ธ Default password is not active, using existing credentials." - SONAR_USER=$DEFAULT_SONAR_USER - SONAR_PASSWORD=$DEFAULT_SONAR_PASSWORD + echo "โŒ Failed to change the password. HTTP status code: $http_status" + echo "Response body: ${response::-3}" + exit 1 fi } From 1dc8e4ccd7a9c630eb576b1cad785b280e6bdb53 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 14 Aug 2024 13:33:31 +0200 Subject: [PATCH 04/40] chore: fix print in password flow --- script/run_analysis.sh | 18 ++++++---- script/sonarqube_management.sh | 62 +++++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/script/run_analysis.sh b/script/run_analysis.sh index 7fd2b76a33..28e9098227 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -24,9 +24,10 @@ OUTPUT_PATH="$(pwd)/output" # Directory for the output NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" -RUN_CLEANUP=false # Set to false to skip cleanup -RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner -WAIT_TIME=60 # Time in seconds to wait after running SonarScanner +RUN_PROJECT_CLEANUP=true # Set to true to delete the existing SonarQube project +RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks +RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner +WAIT_TIME=60 # Time in seconds to wait after running SonarScanner # Run the steps ensure_sonarqube_running @@ -34,11 +35,14 @@ ensure_sonarqube_running # Conditionally reset password reset_sonarqube_password -# Conditionally clean up previous project and token -if $RUN_CLEANUP; then - cleanup_previous_project_and_token +# Conditionally clean up previous project +if $RUN_PROJECT_CLEANUP; then + cleanup_previous_project fi +# Always revoke the existing token +revoke_token + # Create the project and generate the token create_sonarqube_project generate_token @@ -56,6 +60,6 @@ fi run_codecharta_analysis # Final cleanup if enabled -if $RUN_CLEANUP; then +if $RUN_FINAL_CLEANUP; then cleanup fi diff --git a/script/sonarqube_management.sh b/script/sonarqube_management.sh index cf857468b8..364be90338 100644 --- a/script/sonarqube_management.sh +++ b/script/sonarqube_management.sh @@ -52,27 +52,42 @@ reset_sonarqube_password() { # Attempt to log in with the default password to check if it's still active response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") http_status="${response: -3}" - is_valid=$(echo "$response" | jq -r '.valid') + response_body="${response::-3}" - if [ "$http_status" == "200" ] && [ "$is_valid" == "true" ]; then - echo "โœ… Default credentials are valid. Proceeding to change the password..." - change_default_password - else - echo "โ„น๏ธ Default credentials are not in use. Checking the new password..." - - # Attempt to log in with the new password - response=$(curl -u $DEFAULT_SONAR_USER:$NEW_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") - http_status="${response: -3}" - is_valid=$(echo "$response" | jq -r '.valid') + # Check if the response is valid JSON before parsing + if echo "$response_body" | jq -e . >/dev/null 2>&1; then + is_valid=$(echo "$response_body" | jq -r '.valid') if [ "$http_status" == "200" ] && [ "$is_valid" == "true" ]; then - echo "โœ… New password is valid. Proceeding with it." - SONAR_USER=$DEFAULT_SONAR_USER - SONAR_PASSWORD=$NEW_SONAR_PASSWORD + echo "โœ… Default credentials are valid. Proceeding to change the password..." + change_default_password else - echo "โŒ The new password is invalid. Please update the NEW_SONAR_PASSWORD in the script." - exit 1 + echo "โ„น๏ธ Default credentials are not in use. Checking the new password..." + + # Attempt to log in with the new password + response=$(curl -u $DEFAULT_SONAR_USER:$NEW_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") + http_status="${response: -3}" + response_body="${response::-3}" + + if echo "$response_body" | jq -e . >/dev/null 2>&1; then + is_valid=$(echo "$response_body" | jq -r '.valid') + + if [ "$http_status" == "200" ] && [ "$is_valid" == "true" ]; then + echo "โœ… New password is valid. Proceeding with it." + SONAR_USER=$DEFAULT_SONAR_USER + SONAR_PASSWORD=$NEW_SONAR_PASSWORD + else + echo "โŒ The new password is invalid. Please update the NEW_SONAR_PASSWORD in the script." + exit 1 + fi + else + echo "โŒ Failed to parse the response when checking the new password." + exit 1 + fi fi + else + echo "โŒ Failed to parse the response when checking the default password." + exit 1 fi } @@ -83,6 +98,7 @@ change_default_password() { "$HOST_SONAR_URL/api/users/change_password") http_status="${response: -3}" + response_body="${response::-3}" if [ "$http_status" == "200" ] || [ "$http_status" == "204" ]; then echo "โœ… Password has been successfully changed to the new password." @@ -90,14 +106,14 @@ change_default_password() { SONAR_PASSWORD=$NEW_SONAR_PASSWORD else echo "โŒ Failed to change the password. HTTP status code: $http_status" - echo "Response body: ${response::-3}" exit 1 fi } -# Cleanup previous SonarQube project and token -cleanup_previous_project_and_token() { - echo "๐Ÿงน Cleaning up previous SonarQube project and token..." + +# Cleanup previous SonarQube project +cleanup_previous_project() { + echo "๐Ÿงน Cleaning up previous SonarQube project..." # Delete project response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/delete?project=$PROJECT_KEY") @@ -112,6 +128,11 @@ cleanup_previous_project_and_token() { check_response $http_status "$response_body" "Project deletion failed." echo "โœ… Project deleted successfully." fi +} + +# Revoke the existing SonarQube token +revoke_token() { + echo "๐Ÿงน Revoking existing SonarQube token..." # Revoke token response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/revoke?name=$SONARQUBE_TOKEN_NAME") @@ -128,6 +149,7 @@ cleanup_previous_project_and_token() { fi } + # Create SonarQube project only if it doesn't already exist create_sonarqube_project() { echo "๐Ÿ” Checking if project '$PROJECT_KEY' already exists in SonarQube..." From 2e985c11e27a4e8574d061a76c0feaef31653c8d Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 14 Aug 2024 13:45:03 +0200 Subject: [PATCH 05/40] chore: Add dependency_checker.sh script for checking and installing dependencies --- script/dependency_checker.sh | 72 ++++++++++++++++++++++++++++++++++++ script/run_analysis.sh | 1 + 2 files changed, 73 insertions(+) create mode 100644 script/dependency_checker.sh diff --git a/script/dependency_checker.sh b/script/dependency_checker.sh new file mode 100644 index 0000000000..98ea2aa367 --- /dev/null +++ b/script/dependency_checker.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Function to install jq on Ubuntu/Debian +install_jq_ubuntu() { + echo "๐Ÿ”ง Installing jq on Ubuntu/Debian..." + sudo apt-get update + sudo apt-get install -y jq +} + +# Function to install jq on macOS +install_jq_macos() { + echo "๐Ÿ”ง Installing jq on macOS..." + brew install jq +} + +# Function to check the platform and install jq accordingly +install_jq() { + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Check if it's Ubuntu/Debian + if command_exists apt-get; then + install_jq_ubuntu + else + echo "โš ๏ธ Unsupported Linux distribution. Please install jq manually." + exit 1 + fi + elif [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + if command_exists brew; then + install_jq_macos + else + echo "โš ๏ธ Homebrew is not installed. Please install Homebrew first." + echo "๐Ÿ’ป Visit https://brew.sh/ for installation instructions." + exit 1 + fi + else + echo "โš ๏ธ Unsupported OS. Please install jq manually." + exit 1 + fi +} + +# Function to check if jq is installed +check_jq() { + if command_exists jq; then + echo "โœ… jq is already installed." + else + echo "โŒ jq is not installed." + install_jq + fi +} + +# Function to check if docker is installed +check_docker() { + if command_exists docker; then + echo "โœ… Docker is already installed." + else + echo "โŒ Docker is not installed." + echo "๐Ÿ’ป Please install Docker manually." + echo "๐Ÿ”— Visit https://docs.docker.com/get-docker/ for installation instructions." + exit 1 + fi +} + +# Run the checks +check_jq +check_docker + +echo "๐ŸŽ‰ All dependencies are installed." diff --git a/script/run_analysis.sh b/script/run_analysis.sh index 28e9098227..ef2a54f445 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -4,6 +4,7 @@ DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/dependency_checker.sh" . "$DIR/helpers.sh" . "$DIR/cleanup.sh" . "$DIR/sonarqube_management.sh" From 803b518592da641c601fc740a8af897e3f8c0505 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 14 Aug 2024 14:04:11 +0200 Subject: [PATCH 06/40] chore: add variable promting to change the default while running script --- script/run_analysis.sh | 81 +++++++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/script/run_analysis.sh b/script/run_analysis.sh index ef2a54f445..6fb5e93e87 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -10,25 +10,80 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi . "$DIR/sonarqube_management.sh" . "$DIR/analysis.sh" -# Define variables -HOST_SONAR_URL="http://localhost:9000" # Host's URL to access SonarQube -CONTAINER_SONAR_URL="http://sonarqube:9000" # Container's URL to access SonarQube -PROJECT_KEY="test_key" -PROJECT_NAME="test_project" +#!/bin/bash + +# Display a well-formatted introductory text +echo -e "๐Ÿ”ง Welcome to the SonarQube & CodeCharta Automation Script ๐Ÿ”ง" +echo -e "------------------------------------------------------------" +echo -e "This script automates the process of:" +echo -e "1. Setting up a SonarQube project and resetting the default 'admin' password if needed." +echo -e "2. Running SonarScanner to analyze your project's source code." +echo -e "3. Conducting a CodeCharta analysis of the scanned data." +echo -e "\nYou can choose to provide custom values for the project configuration or use the defaults." +echo -e "To skip the prompts and use all default values, run the script with the -s flag." +echo -e "If the default 'admin' password is still in use, the script will change it to the new password you provide." +echo -e "Note: This is only relevant for users who do not already have an instance of SonarQube running." +echo -e "------------------------------------------------------------\n" + +# Check for skip prompt flag +SKIP_PROMPT=false +while getopts ":s" opt; do + case ${opt} in + s ) + SKIP_PROMPT=true + ;; + \? ) + echo "Invalid option: -$OPTARG" 1>&2 + exit 1 + ;; + esac +done + +# Other variables with default values +HOST_SONAR_URL="http://localhost:9000" # URL used by the host machine to access the SonarQube server +CONTAINER_SONAR_URL="http://sonarqube:9000" # URL used by other Docker containers to access the SonarQube server DEFAULT_SONAR_USER="admin" DEFAULT_SONAR_PASSWORD="admin" -NEW_SONAR_PASSWORD="newadminpassword" # Define the new password you want to set -SONARQUBE_TOKEN_NAME="codecharta_token" # Name of the SonarQube token -SONARQUBE_TOKEN="" # Token generated for SonarScanner (optional, will generate a new one if empty) -PROJECT_BASEDIR="$(pwd)/visualization" # Directory to be scanned -OUTPUT_PATH="$(pwd)/output" # Directory for the output +SONARQUBE_TOKEN_NAME="codecharta_token" +SONARQUBE_TOKEN="" +OUTPUT_PATH="$(pwd)/output" NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" RUN_PROJECT_CLEANUP=true # Set to true to delete the existing SonarQube project -RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks -RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner -WAIT_TIME=60 # Time in seconds to wait after running SonarScanner +RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks +RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner +WAIT_TIME=60 # Time in seconds to wait after running SonarScanner + +# If skip prompt mode is not enabled, prompt for important variables with defaults +if [ "$SKIP_PROMPT" = false ]; then + # PROJECT_KEY: A unique identifier for the project in SonarQube. + # Default is set to 'test_key'. + read -p "๐Ÿ”‘ Enter the Project Key (default: test_key): " PROJECT_KEY + PROJECT_KEY=${PROJECT_KEY:-test_key} + + # PROJECT_NAME: The name of the project in SonarQube. + # Default is set to 'test_project'. + read -p "๐Ÿ“› Enter the Project Name (default: test_project): " PROJECT_NAME + PROJECT_NAME=${PROJECT_NAME:-test_project} + + # NEW_SONAR_PASSWORD: The new password to set for the SonarQube admin user. + # If the default 'admin' password is still in use, the script will change it to this value. + # Default is set to 'newadminpassword'. + read -p "๐Ÿ”’ Enter the new password for the SonarQube admin user (default: newadminpassword): " NEW_SONAR_PASSWORD + NEW_SONAR_PASSWORD=${NEW_SONAR_PASSWORD:-newadminpassword} + + # PROJECT_BASEDIR: The directory containing the source code to be analyzed. + # Default is the 'visualization' directory within the current working directory. + read -p "๐Ÿ“ Enter the directory path to be scanned (default: $(pwd)/visualization): " PROJECT_BASEDIR + PROJECT_BASEDIR=${PROJECT_BASEDIR:-$(pwd)/visualization} +else + # Default values if skip prompt is enabled + PROJECT_KEY="test_key" + PROJECT_NAME="test_project" + NEW_SONAR_PASSWORD="newadminpassword" + PROJECT_BASEDIR="$(pwd)/visualization" +fi # Run the steps ensure_sonarqube_running From 698192e377febb5f4321f7c31743ebd99ccf7a9f Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Tue, 13 Aug 2024 13:46:18 +0200 Subject: [PATCH 07/40] feat: Add automated sonarqube and sonarscanner analysis scripts --- script/analysis.sh | 46 ++++++++++ script/cleanup.sh | 10 +++ script/helpers.sh | 77 +++++++++++++++++ script/run_analysis.sh | 36 ++++++++ script/sonarqube_management.sh | 115 +++++++++++++++++++++++++ visualization/sonar-project.properties | 6 +- visualization/sonar.cc.json.gz | Bin 0 -> 2317 bytes 7 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 script/analysis.sh create mode 100644 script/cleanup.sh create mode 100644 script/helpers.sh create mode 100644 script/run_analysis.sh create mode 100644 script/sonarqube_management.sh create mode 100644 visualization/sonar.cc.json.gz diff --git a/script/analysis.sh b/script/analysis.sh new file mode 100644 index 0000000000..a6141d51a8 --- /dev/null +++ b/script/analysis.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Run SonarScanner in the container and capture output +run_sonarscanner() { + echo "๐Ÿ” Running SonarScanner..." + scanner_output=$(docker run --rm --network $NETWORK_NAME \ + -e SONAR_HOST_URL="$CONTAINER_SONAR_URL" \ + -e SONAR_LOGIN="$token" \ + -v "$PROJECT_BASEDIR:/usr/src" \ + sonarsource/sonar-scanner-cli \ + sonar-scanner \ + -Dsonar.projectKey=$PROJECT_KEY \ + -Dsonar.sources=/usr/src 2>&1) + + # Display the output for debugging + echo "$scanner_output" + + if [ $? -ne 0 ]; then + echo "โŒ SonarScanner analysis failed." + exit 1 + fi + echo "โœ… SonarScanner analysis complete." +} + +# Run CodeCharta analysis using docker run +run_codecharta_analysis() { + echo "๐Ÿ“Š Running CodeCharta analysis..." + + # Use the correct hostname 'sonarqube' and execute the analysis + docker run --rm -it --network "$NETWORK_NAME" --name codecharta-analysis \ + -v "$PROJECT_BASEDIR:$PROJECT_BASEDIR" \ + -w "$PROJECT_BASEDIR" \ + codecharta/codecharta-analysis \ + ccsh sonarimport "$CONTAINER_SONAR_URL" "$PROJECT_KEY" "--user-token=$token" "--output-file=./sonar.cc.json" "--merge-modules=false" + + if [ $? -ne 0 ]; then + echo "โŒ CodeCharta analysis failed." + exit 1 + fi + + echo "โœ… CodeCharta analysis complete. Output stored in $OUTPUT_PATH" + + # List the contents of the output directory for verification + echo "Contents of $OUTPUT_PATH:" + ls -l "$OUTPUT_PATH" +} diff --git a/script/cleanup.sh b/script/cleanup.sh new file mode 100644 index 0000000000..2f1f339f89 --- /dev/null +++ b/script/cleanup.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Cleanup function: Stop and remove containers and network +cleanup() { + echo "๐Ÿงน Cleaning up..." + docker stop $SONAR_CONTAINER_NAME 2>/dev/null + docker rm $SONAR_CONTAINER_NAME 2>/dev/null + docker network rm $NETWORK_NAME 2>/dev/null + echo "๐Ÿงน Cleanup complete." +} diff --git a/script/helpers.sh b/script/helpers.sh new file mode 100644 index 0000000000..c588ed7734 --- /dev/null +++ b/script/helpers.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Function to check the last operation result +check_response() { + local http_status=$1 + local response=$2 + local error_message=$3 + + if [[ "$http_status" -ne 200 && "$http_status" -ne 204 ]]; then + case "$http_status" in + 400) + echo "โŒ Failed: $error_message (400 Bad Request)" + ;; + 401) + echo "โŒ Failed: $error_message (401 Unauthorized)" + ;; + 403) + echo "โŒ Failed: $error_message (403 Forbidden)" + ;; + 404) + echo "โŒ Failed: $error_message (404 Not Found)" + ;; + 500) + echo "โŒ Failed: $error_message (500 Internal Server Error)" + ;; + *) + echo "โŒ Failed: $error_message (HTTP status $http_status)" + ;; + esac + + echo "Response:" + echo "$response" | jq '.' + exit 1 + fi +} + +# Function to change the default password if necessary +change_default_password() { + echo "๐Ÿ”‘ Checking if the default password needs to be changed..." + + # Attempt to authenticate with the default password + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s "$HOST_SONAR_URL/api/authentication/validate") + is_valid=$(echo "$response" | jq -r '.valid') + + if [ "$is_valid" == "false" ]; then + echo "๐Ÿ”„ Changing default password..." + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X POST -s \ + -d "login=$DEFAULT_SONAR_USER&previousPassword=$DEFAULT_SONAR_PASSWORD&password=$NEW_SONAR_PASSWORD" \ + "$HOST_SONAR_URL/api/users/change_password") + + if [ "$(echo "$response" | jq -r '.errors')" == "null" ]; then + echo "โœ… Password changed successfully." + else + echo "โŒ Failed to change password. Response: $response" + exit 1 + fi + else + echo "โ„น๏ธ Default password does not need to be changed or has already been changed." + fi +} + +# Function to generate a token for SonarScanner +generate_token() { + echo "๐Ÿ”‘ Generating token..." + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/generate?name=$TOKEN_NAME") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n-1) + check_response $http_status "$response_body" "Token generation failed." + token=$(echo "$response_body" | jq -r '.token') + if [[ -z "$token" || "$token" == "null" ]]; then + echo "โŒ Failed to generate token." + exit 1 + fi + echo "โœ… Token generated: $token" + echo "Token response:" + echo "$response_body" | jq '.' +} diff --git a/script/run_analysis.sh b/script/run_analysis.sh new file mode 100644 index 0000000000..77148ce59e --- /dev/null +++ b/script/run_analysis.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Import the necessary helper scripts +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi + +. "$DIR/helpers.sh" +. "$DIR/cleanup.sh" +. "$DIR/sonarqube_management.sh" +. "$DIR/analysis.sh" + +# Define variables +HOST_SONAR_URL="http://localhost:9000" # Host's URL to access SonarQube +CONTAINER_SONAR_URL="http://sonarqube:9000" # Container's URL to access SonarQube +PROJECT_KEY="test_key" +PROJECT_NAME="test_project" +DEFAULT_SONAR_USER="admin" +DEFAULT_SONAR_PASSWORD="admin" +NEW_SONAR_PASSWORD="newadminpassword" # Define the new password you want to set +TOKEN_NAME="codecharta_token" +PROJECT_BASEDIR="$(pwd)/visualization" # Directory to be scanned +OUTPUT_PATH="$(pwd)/output" # Directory for the output +NETWORK_NAME="sonarnet" +SONAR_CONTAINER_NAME="sonarqube" + +# Run the steps +ensure_sonarqube_running +reset_sonarqube_password +cleanup_previous_project_and_token +create_sonarqube_project +generate_token +run_sonarscanner +run_codecharta_analysis + +# Final cleanup +cleanup diff --git a/script/sonarqube_management.sh b/script/sonarqube_management.sh new file mode 100644 index 0000000000..2c9facc47d --- /dev/null +++ b/script/sonarqube_management.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +# Ensure the Docker network exists +ensure_network_exists() { + if ! docker network inspect $NETWORK_NAME >/dev/null 2>&1; then + echo "๐Ÿ”ง Creating Docker network $NETWORK_NAME..." + docker network create $NETWORK_NAME + if [ $? -ne 0 ]; then + echo "โŒ Failed to create Docker network $NETWORK_NAME." + exit 1 + fi + else + echo "โ„น๏ธ Docker network $NETWORK_NAME already exists." + fi +} + +# Step 1: Ensure the SonarQube container is running +ensure_sonarqube_running() { + # Ensure the Docker network exists before running the container + ensure_network_exists + + # Check if the SonarQube container is already running with the correct settings + existing_container=$(docker ps -a -q -f name=$SONAR_CONTAINER_NAME) + if [ "$existing_container" ]; then + running_container=$(docker ps -q -f name=$SONAR_CONTAINER_NAME) + if [ "$running_container" ]; then + echo "โ„น๏ธ SonarQube container is already running." + return + else + echo "โš ๏ธ SonarQube container exists but is not running. Starting it..." + docker start $SONAR_CONTAINER_NAME + if [ $? -ne 0 ]; then + echo "โŒ Failed to start existing SonarQube container." + exit 1 + fi + return + fi + fi + + # If no container exists, create and start a new one + echo "๐Ÿš€ Starting SonarQube container..." + docker run -d --name $SONAR_CONTAINER_NAME --network $NETWORK_NAME -p 9000:9000 sonarqube:community + + # Wait for SonarQube to be ready only after a new container is created + echo "โณ Waiting for SonarQube to be ready..." + sleep 60 # Adjust this sleep time as needed to allow SonarQube to fully start +} +# Step 2: Reset the password if needed +reset_sonarqube_password() { + # Attempt to log in with the default password to check if it's still active + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s "$HOST_SONAR_URL/api/authentication/validate") + is_valid=$(echo "$response" | jq -r '.valid') + + if [ "$is_valid" == "false" ]; then + # The default password is still active, so we need to change it + echo "๐Ÿ”„ Changing default SonarQube password..." + change_default_password + SONAR_USER=$DEFAULT_SONAR_USER + SONAR_PASSWORD=$NEW_SONAR_PASSWORD + else + echo "โ„น๏ธ Default password is not active, using existing credentials." + SONAR_USER=$DEFAULT_SONAR_USER + SONAR_PASSWORD=$DEFAULT_SONAR_PASSWORD + fi +} + +# Cleanup previous SonarQube project and token +cleanup_previous_project_and_token() { + echo "๐Ÿงน Cleaning up previous SonarQube project and token..." + + # Delete project + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/delete?project=$PROJECT_KEY") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n-1) + if [ "$http_status" -eq 404 ]; then + echo "โ„น๏ธ Project not found, skipping deletion." + elif [ -z "$http_status" ]; then + echo "โŒ Failed to connect to SonarQube. Ensure that SonarQube is running and accessible at $HOST_SONAR_URL." + exit 1 + else + check_response $http_status "$response_body" "Project deletion failed." + echo "โœ… Project deleted successfully." + fi + + # Revoke token + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/revoke?name=$TOKEN_NAME") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n-1) + if [ "$http_status" -eq 404 ]; then + echo "โ„น๏ธ Token not found, skipping revocation." + elif [ -z "$http_status" ]; then + echo "โŒ Failed to connect to SonarQube. Ensure that SonarQube is running and accessible at $HOST_SONAR_URL." + exit 1 + else + check_response $http_status "$response_body" "Token revocation failed." + echo "โœ… Token revoked successfully." + fi +} + +# Create SonarQube project +create_sonarqube_project() { + echo "๐Ÿš€ Creating project in SonarQube..." + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/create?project=$PROJECT_KEY&name=$PROJECT_NAME") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n-1) + if [[ "$http_status" -eq 404 ]]; then + echo "โŒ Failed: Project creation failed. The endpoint may be incorrect or deprecated." + echo "Response: $response_body" + exit 1 + fi + check_response $http_status "$response_body" "Project creation failed." + echo "โœ… Project created successfully." + echo "Project creation response:" + echo "$response_body" | jq '.' +} diff --git a/visualization/sonar-project.properties b/visualization/sonar-project.properties index d0f8962ea2..8e5b751488 100644 --- a/visualization/sonar-project.properties +++ b/visualization/sonar-project.properties @@ -1,4 +1,4 @@ -sonar.projectKey=maibornwolff-gmbh_codecharta_visualization +# sonar.projectKey=maibornwolff-gmbh_codecharta_visualization sonar.projectName=CodeCharta Visualization sonar.sources=./app sonar.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts,**/*.spec.js,**/*.e2e.ts,**/*.e2e.js,**/*.po.ts,**/app/codeCharta/util/testUtils/* @@ -7,8 +7,8 @@ sonar.test.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts sonar.test.inclusions=**/*.spec.ts sonar.coverage.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts,**/*.spec.js,**/*.e2e.ts,**/*.e2e.js,**/*.po.ts,**/*.html,**/*.scss,**/app/codeCharta/util/dataMocks.ts,**/app/codeCharta/util/testUtils/* sonar.cpd.exclusions=**/app/codeCharta/util/dataMocks.ts -sonar.host.url=https://sonarcloud.io -sonar.organization=maibornwolff-gmbh +# sonar.host.url=https://sonarcloud.io +# sonar.organization=maibornwolff-gmbh sonar.links.ci=https://travis-ci.org/MaibornWolff/codecharta sonar.links.issue=https://github.com/MaibornWolff/codecharta/issues # Disabling rules diff --git a/visualization/sonar.cc.json.gz b/visualization/sonar.cc.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..5a17d871d727438bd3f4d7994f05138565d05796 GIT binary patch literal 2317 zcmV+o3G((IiwFP!000000Oed?Z`(K$|0;x^xj})X~YSuanH^KR9zA=!#EX zO(st#N+;Y-Uj6=cqC%U|+CjU!%kbBwmI*iTn>uI2Ja@?a_1n{l6zV4Qz)~R-!xeP$ z<=YqF%*1DItkBRhIeYd3Sa`DZgM6v=^9cqh`r5W#aLbIyoHj_ASgoj;QO0=Ycrp{# z=Khh16Hemnel1AkZ6H0JBt;Kl^etVK^i(MKPH#R_nHK`UmN#OV)|v`zsdJfqb$Xg; zW}o@FFsbw8nb4<+ST2QHo+O-S+?=jB0ATQIA(X(e?J3Sh7!b9e$;lbO3~=UBvm0(^ zYoR6eymdsc1C@NPs7+9xw4;(RohwK5qJW0qT*`^%Yi@+QBgHUrK+cytAV1`*Ibeh- zzeY+EU`2)pV_G^UskJkvSDECuAmlox=^c}Tktwo}X~oh6_nzL0RlXt&`1J~wn31YO zGN%@_RV$KFms(g+qw{4-DDgv+`5k%BrgYBZ5eM&C*=a%SU)m^8_YA6t`%t z1?4FLK-?@S!|z<>D@&+K;G@6G(8mqp_bkVrXRbV5-{ z;F&oMzaZw^^$#T(t$gaq193pby-5X2aa)uLnWJsMlOPxXpDWxvAUud=Yg7wiR-H-7 zw50WACEWo$_{QK5%BNwAbdfAKWE6r?*^{$O3-aTWmU5sdP$zi?Zk%G~>657)%okfC zc^!!T3SM;-vNF335Msl*5R&)7y!5nwl>S-J;xpqf5RGD`$=Trb1*_Y+ z+5EdCjqWwuPNGVan*bGb$GQQ2sflR6kgx zeFw1E&4Iz1hTYz4*J+rpOQ|a>_XNI5PA%Yv42h-GfHQ`tg0c%s)V8hY0B_fJ26yX zw7r1UM*2b+P#h;#ye#g)l!CJaEJ7#oh^hC0-iv7XkRCxrc2JL)dQN2#&|^+!k9Uzr ziuOS;_bJ>%r>A+A_;~#oi}+qB!=-I6ls(GZqb{Nx=Xlj80Vq;u{$!YG>zZP^sOX?M z)^*eoD+cVKtwNFHcOLCf@!!M!E(ZX(=*J*~Rt2LWg2$E2k4g$%rSvXoJfxl;wIBx4 zf5bml0vL6X*#Ggm$F_y+#86`fQeXA>DqdTFy^+J-MT1_T#}zjW6%kM#qlJV~bMPY^ zuRRPE9=w3YiVx!g@;w}_fGlS@;+Un{*$h5S30)TqcXeR1-u6!%`_3CX4M?r!I2whVl>0Cy#E&)ldpxZqB%cTd_Z#9-q zj5`eRAvGLLqWk!4t6v`rrU#i^IYLBcH<-QLCv#~9n{mP$N=<%bS`w)=hVqXem3zb? ziW`w-par2C)oJPl3;9$Ota_mqe34bRhSI3NCWSh*7|_Y$Zqu{A*A;zWaqCQnUsP1D z88@bQu|gXZDl7R{VCY9xscN_-lhQSW{;(PIIO2N`6X%ngzKz%ey)0QC1U1~wqzZWW zZ3feTCVq<-V6c-I@!f43YzGPJE+{*ifn6!3cT_3fPReSr_JwMiLVR3s1wa`Fo2 z-kEuXS^3;h#nNzgMwfi&l`ozY4$hD?a9+H~rM#<7#7nfriR+;&#Cu7HUYOvE!)d+jdbX(Iyh5%s3L=@A~C-}zK9t92TK zB>F2s`;~y&6ybf!?eByETHM zH}DEYw&maOCFP+jBDL1B`&VK&q n%a_m3U(oZjUwE=OJ3l{rPU(yD#fx9~ Date: Tue, 13 Aug 2024 16:39:52 +0200 Subject: [PATCH 08/40] chore: Update SonarQube and SonarScanner analysis script --- script/analysis.sh | 14 +- script/helpers.sh | 67 ++- script/run_analysis.sh | 35 +- script/sonarqube_management.sh | 23 +- visualization/sonar-project.properties | 6 +- visualization/sonar.cc.json.gz | Bin 2317 -> 74900 bytes visualization/sonar.cc.json/sonar.cc.json | 685 ++++++++++++++++++++++ 7 files changed, 801 insertions(+), 29 deletions(-) create mode 100644 visualization/sonar.cc.json/sonar.cc.json diff --git a/script/analysis.sh b/script/analysis.sh index a6141d51a8..278d80f14f 100644 --- a/script/analysis.sh +++ b/script/analysis.sh @@ -3,17 +3,15 @@ # Run SonarScanner in the container and capture output run_sonarscanner() { echo "๐Ÿ” Running SonarScanner..." - scanner_output=$(docker run --rm --network $NETWORK_NAME \ - -e SONAR_HOST_URL="$CONTAINER_SONAR_URL" \ - -e SONAR_LOGIN="$token" \ + docker run --rm \ + --network $NETWORK_NAME \ -v "$PROJECT_BASEDIR:/usr/src" \ sonarsource/sonar-scanner-cli \ sonar-scanner \ -Dsonar.projectKey=$PROJECT_KEY \ - -Dsonar.sources=/usr/src 2>&1) - - # Display the output for debugging - echo "$scanner_output" + -Dsonar.sources=/usr/src \ + -Dsonar.token=$token \ + -Dsonar.host.url="$CONTAINER_SONAR_URL" if [ $? -ne 0 ]; then echo "โŒ SonarScanner analysis failed." @@ -31,7 +29,7 @@ run_codecharta_analysis() { -v "$PROJECT_BASEDIR:$PROJECT_BASEDIR" \ -w "$PROJECT_BASEDIR" \ codecharta/codecharta-analysis \ - ccsh sonarimport "$CONTAINER_SONAR_URL" "$PROJECT_KEY" "--user-token=$token" "--output-file=./sonar.cc.json" "--merge-modules=false" + ccsh sonarimport "$CONTAINER_SONAR_URL" "$PROJECT_KEY" "--user-token=$token" "--output-file=sonar.cc.json" "--merge-modules=false" if [ $? -ne 0 ]; then echo "โŒ CodeCharta analysis failed." diff --git a/script/helpers.sh b/script/helpers.sh index c588ed7734..20132c0079 100644 --- a/script/helpers.sh +++ b/script/helpers.sh @@ -39,9 +39,9 @@ change_default_password() { echo "๐Ÿ”‘ Checking if the default password needs to be changed..." # Attempt to authenticate with the default password - response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s "$HOST_SONAR_URL/api/authentication/validate") + response=$(curl -X GET -s "$HOST_SONAR_URL/api/authentication/validate") is_valid=$(echo "$response" | jq -r '.valid') - + echo "Response: $response" if [ "$is_valid" == "false" ]; then echo "๐Ÿ”„ Changing default password..." response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X POST -s \ @@ -59,19 +59,64 @@ change_default_password() { fi } -# Function to generate a token for SonarScanner +# Function to check if a token is valid +is_valid_token() { + local token=$1 + if [[ "$token" =~ ^squ_[0-9a-f]{40}$ ]]; then + return 0 # Valid token + else + return 1 # Invalid token + fi +} + +# Function to check if a token exists (but note: SonarQube does not return the token value once created) +token_exists() { + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X GET -s "$HOST_SONAR_URL/api/user_tokens/search") + + # Directly parse the response to check if the token exists + existing_token_name=$(echo "$response" | jq -r ".userTokens[] | select(.name == \"$SONARQUBE_TOKEN_NAME\") | .name") + + if [[ "$existing_token_name" == "$SONARQUBE_TOKEN_NAME" ]]; then + return 0 # Token exists + else + return 1 # Token does not exist + fi +} + +# Function to generate a token for SonarScanner only if it doesn't already exist generate_token() { + if [[ -n "$SONARQUBE_TOKEN" ]]; then + echo "โ„น๏ธ Using predefined token." + token=$SONARQUBE_TOKEN + if ! is_valid_token "$token"; then + echo "โŒ Predefined token is invalid." + exit 1 + fi + return + fi + + echo "๐Ÿ” Checking if token '$SONARQUBE_TOKEN_NAME' already exists..." + + # Check if the token exists + if token_exists; then + echo "โŒ Token '$SONARQUBE_TOKEN_NAME' already exists. Cannot retrieve the value after generation." + exit 1 + fi + + # If the token does not exist, generate a new one echo "๐Ÿ”‘ Generating token..." - response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/generate?name=$TOKEN_NAME") - http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n-1) - check_response $http_status "$response_body" "Token generation failed." - token=$(echo "$response_body" | jq -r '.token') - if [[ -z "$token" || "$token" == "null" ]]; then - echo "โŒ Failed to generate token." + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s "$HOST_SONAR_URL/api/user_tokens/generate?name=$SONARQUBE_TOKEN_NAME") + + # Extract the newly generated token from the response + token=$(echo "$response" | jq -r '.token') + + # Since this is a new token, ensure it follows the correct format + if ! is_valid_token "$token"; then + echo "โŒ Failed to generate a valid token." exit 1 fi + echo "โœ… Token generated: $token" echo "Token response:" - echo "$response_body" | jq '.' + echo "$response" | jq '.' } diff --git a/script/run_analysis.sh b/script/run_analysis.sh index 77148ce59e..0a774dbf67 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -17,20 +17,45 @@ PROJECT_NAME="test_project" DEFAULT_SONAR_USER="admin" DEFAULT_SONAR_PASSWORD="admin" NEW_SONAR_PASSWORD="newadminpassword" # Define the new password you want to set -TOKEN_NAME="codecharta_token" +SONARQUBE_TOKEN_NAME="codecharta_token" # Name of the SonarQube token +SONARQUBE_TOKEN="" # Token generated for SonarScanner (optional, will generate a new one if empty) PROJECT_BASEDIR="$(pwd)/visualization" # Directory to be scanned OUTPUT_PATH="$(pwd)/output" # Directory for the output NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" +RUN_CLEANUP=true # Set to false to skip cleanup +RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner +WAIT_TIME=60 # Time in seconds to wait after running SonarScanner + # Run the steps ensure_sonarqube_running + +# Conditionally reset password reset_sonarqube_password -cleanup_previous_project_and_token + +# Conditionally clean up previous project and token +if $RUN_CLEANUP; then + cleanup_previous_project_and_token +fi + +# Create the project and generate the token create_sonarqube_project generate_token -run_sonarscanner + +# Conditionally run the SonarScanner +if $RUN_SONAR_SCANNER; then + run_sonarscanner + + # Wait for the data to be fully uploaded to SonarQube + echo "โณ Waiting for $WAIT_TIME seconds to ensure data is uploaded to SonarQube..." + sleep $WAIT_TIME +fi + +# Run the CodeCharta analysis run_codecharta_analysis -# Final cleanup -cleanup +# Final cleanup if enabled +if $RUN_CLEANUP; then + cleanup +fi diff --git a/script/sonarqube_management.sh b/script/sonarqube_management.sh index 2c9facc47d..53e940d413 100644 --- a/script/sonarqube_management.sh +++ b/script/sonarqube_management.sh @@ -83,7 +83,7 @@ cleanup_previous_project_and_token() { fi # Revoke token - response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/revoke?name=$TOKEN_NAME") + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/revoke?name=$SONARQUBE_TOKEN_NAME") http_status=$(echo "$response" | tail -n1) response_body=$(echo "$response" | head -n-1) if [ "$http_status" -eq 404 ]; then @@ -97,17 +97,36 @@ cleanup_previous_project_and_token() { fi } -# Create SonarQube project +# Create SonarQube project only if it doesn't already exist create_sonarqube_project() { + echo "๐Ÿ” Checking if project '$PROJECT_KEY' already exists in SonarQube..." + + # Check if the project already exists + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X GET -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/search?projects=$PROJECT_KEY") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n-1) + + if [[ "$http_status" -eq 200 && $(echo "$response_body" | jq -r '.components | length') -gt 0 ]]; then + echo "โ„น๏ธ Project '$PROJECT_KEY' already exists. Skipping creation." + return + elif [[ "$http_status" -eq 404 ]]; then + echo "โŒ Failed: Unable to check if the project exists. The endpoint may be incorrect or deprecated." + echo "Response: $response_body" + exit 1 + fi + + # If the project does not exist, create it echo "๐Ÿš€ Creating project in SonarQube..." response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/create?project=$PROJECT_KEY&name=$PROJECT_NAME") http_status=$(echo "$response" | tail -n1) response_body=$(echo "$response" | head -n-1) + if [[ "$http_status" -eq 404 ]]; then echo "โŒ Failed: Project creation failed. The endpoint may be incorrect or deprecated." echo "Response: $response_body" exit 1 fi + check_response $http_status "$response_body" "Project creation failed." echo "โœ… Project created successfully." echo "Project creation response:" diff --git a/visualization/sonar-project.properties b/visualization/sonar-project.properties index 8e5b751488..d0f8962ea2 100644 --- a/visualization/sonar-project.properties +++ b/visualization/sonar-project.properties @@ -1,4 +1,4 @@ -# sonar.projectKey=maibornwolff-gmbh_codecharta_visualization +sonar.projectKey=maibornwolff-gmbh_codecharta_visualization sonar.projectName=CodeCharta Visualization sonar.sources=./app sonar.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts,**/*.spec.js,**/*.e2e.ts,**/*.e2e.js,**/*.po.ts,**/app/codeCharta/util/testUtils/* @@ -7,8 +7,8 @@ sonar.test.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts sonar.test.inclusions=**/*.spec.ts sonar.coverage.exclusions=**/app/puppeteer.helper.ts,**/node_modules/**,**/*.spec.ts,**/*.spec.js,**/*.e2e.ts,**/*.e2e.js,**/*.po.ts,**/*.html,**/*.scss,**/app/codeCharta/util/dataMocks.ts,**/app/codeCharta/util/testUtils/* sonar.cpd.exclusions=**/app/codeCharta/util/dataMocks.ts -# sonar.host.url=https://sonarcloud.io -# sonar.organization=maibornwolff-gmbh +sonar.host.url=https://sonarcloud.io +sonar.organization=maibornwolff-gmbh sonar.links.ci=https://travis-ci.org/MaibornWolff/codecharta sonar.links.issue=https://github.com/MaibornWolff/codecharta/issues # Disabling rules diff --git a/visualization/sonar.cc.json.gz b/visualization/sonar.cc.json.gz index 5a17d871d727438bd3f4d7994f05138565d05796..87bde899d2c8b7d1119867e4a2b9c3ea2393ed24 100644 GIT binary patch literal 74900 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%0Ane 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 literal 2317 zcmV+o3G((IiwFP!000000Oed?Z`(K$|0;x^xj})X~YSuanH^KR9zA=!#EX zO(st#N+;Y-Uj6=cqC%U|+CjU!%kbBwmI*iTn>uI2Ja@?a_1n{l6zV4Qz)~R-!xeP$ z<=YqF%*1DItkBRhIeYd3Sa`DZgM6v=^9cqh`r5W#aLbIyoHj_ASgoj;QO0=Ycrp{# z=Khh16Hemnel1AkZ6H0JBt;Kl^etVK^i(MKPH#R_nHK`UmN#OV)|v`zsdJfqb$Xg; zW}o@FFsbw8nb4<+ST2QHo+O-S+?=jB0ATQIA(X(e?J3Sh7!b9e$;lbO3~=UBvm0(^ zYoR6eymdsc1C@NPs7+9xw4;(RohwK5qJW0qT*`^%Yi@+QBgHUrK+cytAV1`*Ibeh- zzeY+EU`2)pV_G^UskJkvSDECuAmlox=^c}Tktwo}X~oh6_nzL0RlXt&`1J~wn31YO zGN%@_RV$KFms(g+qw{4-DDgv+`5k%BrgYBZ5eM&C*=a%SU)m^8_YA6t`%t z1?4FLK-?@S!|z<>D@&+K;G@6G(8mqp_bkVrXRbV5-{ z;F&oMzaZw^^$#T(t$gaq193pby-5X2aa)uLnWJsMlOPxXpDWxvAUud=Yg7wiR-H-7 zw50WACEWo$_{QK5%BNwAbdfAKWE6r?*^{$O3-aTWmU5sdP$zi?Zk%G~>657)%okfC zc^!!T3SM;-vNF335Msl*5R&)7y!5nwl>S-J;xpqf5RGD`$=Trb1*_Y+ z+5EdCjqWwuPNGVan*bGb$GQQ2sflR6kgx zeFw1E&4Iz1hTYz4*J+rpOQ|a>_XNI5PA%Yv42h-GfHQ`tg0c%s)V8hY0B_fJ26yX zw7r1UM*2b+P#h;#ye#g)l!CJaEJ7#oh^hC0-iv7XkRCxrc2JL)dQN2#&|^+!k9Uzr ziuOS;_bJ>%r>A+A_;~#oi}+qB!=-I6ls(GZqb{Nx=Xlj80Vq;u{$!YG>zZP^sOX?M z)^*eoD+cVKtwNFHcOLCf@!!M!E(ZX(=*J*~Rt2LWg2$E2k4g$%rSvXoJfxl;wIBx4 zf5bml0vL6X*#Ggm$F_y+#86`fQeXA>DqdTFy^+J-MT1_T#}zjW6%kM#qlJV~bMPY^ zuRRPE9=w3YiVx!g@;w}_fGlS@;+Un{*$h5S30)TqcXeR1-u6!%`_3CX4M?r!I2whVl>0Cy#E&)ldpxZqB%cTd_Z#9-q zj5`eRAvGLLqWk!4t6v`rrU#i^IYLBcH<-QLCv#~9n{mP$N=<%bS`w)=hVqXem3zb? ziW`w-par2C)oJPl3;9$Ota_mqe34bRhSI3NCWSh*7|_Y$Zqu{A*A;zWaqCQnUsP1D z88@bQu|gXZDl7R{VCY9xscN_-lhQSW{;(PIIO2N`6X%ngzKz%ey)0QC1U1~wqzZWW zZ3feTCVq<-V6c-I@!f43YzGPJE+{*ifn6!3cT_3fPReSr_JwMiLVR3s1wa`Fo2 z-kEuXS^3;h#nNzgMwfi&l`ozY4$hD?a9+H~rM#<7#7nfriR+;&#Cu7HUYOvE!)d+jdbX(Iyh5%s3L=@A~C-}zK9t92TK zB>F2s`;~y&6ybf!?eByETHM zH}DEYw&maOCFP+jBDL1B`&VK&q n%a_m3U(oZjUwE=OJ3l{rPU(yD#fx9~ Date: Wed, 14 Aug 2024 13:24:42 +0200 Subject: [PATCH 09/40] chore: update password flow --- script/run_analysis.sh | 2 +- script/sonarqube_management.sh | 47 ++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/script/run_analysis.sh b/script/run_analysis.sh index 0a774dbf67..7fd2b76a33 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -24,7 +24,7 @@ OUTPUT_PATH="$(pwd)/output" # Directory for the output NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" -RUN_CLEANUP=true # Set to false to skip cleanup +RUN_CLEANUP=false # Set to false to skip cleanup RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner WAIT_TIME=60 # Time in seconds to wait after running SonarScanner diff --git a/script/sonarqube_management.sh b/script/sonarqube_management.sh index 53e940d413..cf857468b8 100644 --- a/script/sonarqube_management.sh +++ b/script/sonarqube_management.sh @@ -45,22 +45,53 @@ ensure_sonarqube_running() { echo "โณ Waiting for SonarQube to be ready..." sleep 60 # Adjust this sleep time as needed to allow SonarQube to fully start } -# Step 2: Reset the password if needed + reset_sonarqube_password() { + echo "๐Ÿ” Testing SonarQube credentials: Username='$DEFAULT_SONAR_USER', Password='$DEFAULT_SONAR_PASSWORD'" + # Attempt to log in with the default password to check if it's still active - response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s "$HOST_SONAR_URL/api/authentication/validate") + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") + http_status="${response: -3}" is_valid=$(echo "$response" | jq -r '.valid') - if [ "$is_valid" == "false" ]; then - # The default password is still active, so we need to change it - echo "๐Ÿ”„ Changing default SonarQube password..." + if [ "$http_status" == "200" ] && [ "$is_valid" == "true" ]; then + echo "โœ… Default credentials are valid. Proceeding to change the password..." change_default_password + else + echo "โ„น๏ธ Default credentials are not in use. Checking the new password..." + + # Attempt to log in with the new password + response=$(curl -u $DEFAULT_SONAR_USER:$NEW_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") + http_status="${response: -3}" + is_valid=$(echo "$response" | jq -r '.valid') + + if [ "$http_status" == "200" ] && [ "$is_valid" == "true" ]; then + echo "โœ… New password is valid. Proceeding with it." + SONAR_USER=$DEFAULT_SONAR_USER + SONAR_PASSWORD=$NEW_SONAR_PASSWORD + else + echo "โŒ The new password is invalid. Please update the NEW_SONAR_PASSWORD in the script." + exit 1 + fi + fi +} + +# Function to change the default password to a new password +change_default_password() { + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X POST -s -w "%{http_code}" \ + -d "login=$DEFAULT_SONAR_USER&previousPassword=$DEFAULT_SONAR_PASSWORD&password=$NEW_SONAR_PASSWORD" \ + "$HOST_SONAR_URL/api/users/change_password") + + http_status="${response: -3}" + + if [ "$http_status" == "200" ] || [ "$http_status" == "204" ]; then + echo "โœ… Password has been successfully changed to the new password." SONAR_USER=$DEFAULT_SONAR_USER SONAR_PASSWORD=$NEW_SONAR_PASSWORD else - echo "โ„น๏ธ Default password is not active, using existing credentials." - SONAR_USER=$DEFAULT_SONAR_USER - SONAR_PASSWORD=$DEFAULT_SONAR_PASSWORD + echo "โŒ Failed to change the password. HTTP status code: $http_status" + echo "Response body: ${response::-3}" + exit 1 fi } From c8e0acb5ae8aeba6f68089d0d11fbe00daa90ed3 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 14 Aug 2024 13:33:31 +0200 Subject: [PATCH 10/40] chore: fix print in password flow --- script/run_analysis.sh | 18 ++++++---- script/sonarqube_management.sh | 62 +++++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/script/run_analysis.sh b/script/run_analysis.sh index 7fd2b76a33..28e9098227 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -24,9 +24,10 @@ OUTPUT_PATH="$(pwd)/output" # Directory for the output NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" -RUN_CLEANUP=false # Set to false to skip cleanup -RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner -WAIT_TIME=60 # Time in seconds to wait after running SonarScanner +RUN_PROJECT_CLEANUP=true # Set to true to delete the existing SonarQube project +RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks +RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner +WAIT_TIME=60 # Time in seconds to wait after running SonarScanner # Run the steps ensure_sonarqube_running @@ -34,11 +35,14 @@ ensure_sonarqube_running # Conditionally reset password reset_sonarqube_password -# Conditionally clean up previous project and token -if $RUN_CLEANUP; then - cleanup_previous_project_and_token +# Conditionally clean up previous project +if $RUN_PROJECT_CLEANUP; then + cleanup_previous_project fi +# Always revoke the existing token +revoke_token + # Create the project and generate the token create_sonarqube_project generate_token @@ -56,6 +60,6 @@ fi run_codecharta_analysis # Final cleanup if enabled -if $RUN_CLEANUP; then +if $RUN_FINAL_CLEANUP; then cleanup fi diff --git a/script/sonarqube_management.sh b/script/sonarqube_management.sh index cf857468b8..364be90338 100644 --- a/script/sonarqube_management.sh +++ b/script/sonarqube_management.sh @@ -52,27 +52,42 @@ reset_sonarqube_password() { # Attempt to log in with the default password to check if it's still active response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") http_status="${response: -3}" - is_valid=$(echo "$response" | jq -r '.valid') + response_body="${response::-3}" - if [ "$http_status" == "200" ] && [ "$is_valid" == "true" ]; then - echo "โœ… Default credentials are valid. Proceeding to change the password..." - change_default_password - else - echo "โ„น๏ธ Default credentials are not in use. Checking the new password..." - - # Attempt to log in with the new password - response=$(curl -u $DEFAULT_SONAR_USER:$NEW_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") - http_status="${response: -3}" - is_valid=$(echo "$response" | jq -r '.valid') + # Check if the response is valid JSON before parsing + if echo "$response_body" | jq -e . >/dev/null 2>&1; then + is_valid=$(echo "$response_body" | jq -r '.valid') if [ "$http_status" == "200" ] && [ "$is_valid" == "true" ]; then - echo "โœ… New password is valid. Proceeding with it." - SONAR_USER=$DEFAULT_SONAR_USER - SONAR_PASSWORD=$NEW_SONAR_PASSWORD + echo "โœ… Default credentials are valid. Proceeding to change the password..." + change_default_password else - echo "โŒ The new password is invalid. Please update the NEW_SONAR_PASSWORD in the script." - exit 1 + echo "โ„น๏ธ Default credentials are not in use. Checking the new password..." + + # Attempt to log in with the new password + response=$(curl -u $DEFAULT_SONAR_USER:$NEW_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") + http_status="${response: -3}" + response_body="${response::-3}" + + if echo "$response_body" | jq -e . >/dev/null 2>&1; then + is_valid=$(echo "$response_body" | jq -r '.valid') + + if [ "$http_status" == "200" ] && [ "$is_valid" == "true" ]; then + echo "โœ… New password is valid. Proceeding with it." + SONAR_USER=$DEFAULT_SONAR_USER + SONAR_PASSWORD=$NEW_SONAR_PASSWORD + else + echo "โŒ The new password is invalid. Please update the NEW_SONAR_PASSWORD in the script." + exit 1 + fi + else + echo "โŒ Failed to parse the response when checking the new password." + exit 1 + fi fi + else + echo "โŒ Failed to parse the response when checking the default password." + exit 1 fi } @@ -83,6 +98,7 @@ change_default_password() { "$HOST_SONAR_URL/api/users/change_password") http_status="${response: -3}" + response_body="${response::-3}" if [ "$http_status" == "200" ] || [ "$http_status" == "204" ]; then echo "โœ… Password has been successfully changed to the new password." @@ -90,14 +106,14 @@ change_default_password() { SONAR_PASSWORD=$NEW_SONAR_PASSWORD else echo "โŒ Failed to change the password. HTTP status code: $http_status" - echo "Response body: ${response::-3}" exit 1 fi } -# Cleanup previous SonarQube project and token -cleanup_previous_project_and_token() { - echo "๐Ÿงน Cleaning up previous SonarQube project and token..." + +# Cleanup previous SonarQube project +cleanup_previous_project() { + echo "๐Ÿงน Cleaning up previous SonarQube project..." # Delete project response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/delete?project=$PROJECT_KEY") @@ -112,6 +128,11 @@ cleanup_previous_project_and_token() { check_response $http_status "$response_body" "Project deletion failed." echo "โœ… Project deleted successfully." fi +} + +# Revoke the existing SonarQube token +revoke_token() { + echo "๐Ÿงน Revoking existing SonarQube token..." # Revoke token response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/revoke?name=$SONARQUBE_TOKEN_NAME") @@ -128,6 +149,7 @@ cleanup_previous_project_and_token() { fi } + # Create SonarQube project only if it doesn't already exist create_sonarqube_project() { echo "๐Ÿ” Checking if project '$PROJECT_KEY' already exists in SonarQube..." From 0c25bff671ee214fedaa6b8ebf51aeb2243a3599 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 14 Aug 2024 13:45:03 +0200 Subject: [PATCH 11/40] chore: Add dependency_checker.sh script for checking and installing dependencies --- script/dependency_checker.sh | 72 ++++++++++++++++++++++++++++++++++++ script/run_analysis.sh | 1 + 2 files changed, 73 insertions(+) create mode 100644 script/dependency_checker.sh diff --git a/script/dependency_checker.sh b/script/dependency_checker.sh new file mode 100644 index 0000000000..98ea2aa367 --- /dev/null +++ b/script/dependency_checker.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Function to install jq on Ubuntu/Debian +install_jq_ubuntu() { + echo "๐Ÿ”ง Installing jq on Ubuntu/Debian..." + sudo apt-get update + sudo apt-get install -y jq +} + +# Function to install jq on macOS +install_jq_macos() { + echo "๐Ÿ”ง Installing jq on macOS..." + brew install jq +} + +# Function to check the platform and install jq accordingly +install_jq() { + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Check if it's Ubuntu/Debian + if command_exists apt-get; then + install_jq_ubuntu + else + echo "โš ๏ธ Unsupported Linux distribution. Please install jq manually." + exit 1 + fi + elif [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + if command_exists brew; then + install_jq_macos + else + echo "โš ๏ธ Homebrew is not installed. Please install Homebrew first." + echo "๐Ÿ’ป Visit https://brew.sh/ for installation instructions." + exit 1 + fi + else + echo "โš ๏ธ Unsupported OS. Please install jq manually." + exit 1 + fi +} + +# Function to check if jq is installed +check_jq() { + if command_exists jq; then + echo "โœ… jq is already installed." + else + echo "โŒ jq is not installed." + install_jq + fi +} + +# Function to check if docker is installed +check_docker() { + if command_exists docker; then + echo "โœ… Docker is already installed." + else + echo "โŒ Docker is not installed." + echo "๐Ÿ’ป Please install Docker manually." + echo "๐Ÿ”— Visit https://docs.docker.com/get-docker/ for installation instructions." + exit 1 + fi +} + +# Run the checks +check_jq +check_docker + +echo "๐ŸŽ‰ All dependencies are installed." diff --git a/script/run_analysis.sh b/script/run_analysis.sh index 28e9098227..ef2a54f445 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -4,6 +4,7 @@ DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/dependency_checker.sh" . "$DIR/helpers.sh" . "$DIR/cleanup.sh" . "$DIR/sonarqube_management.sh" From 2e8584eb5750af454f8e3207c16f368407e985fc Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 14 Aug 2024 14:04:11 +0200 Subject: [PATCH 12/40] chore: add variable promting to change the default while running script --- script/run_analysis.sh | 81 +++++++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/script/run_analysis.sh b/script/run_analysis.sh index ef2a54f445..6fb5e93e87 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -10,25 +10,80 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi . "$DIR/sonarqube_management.sh" . "$DIR/analysis.sh" -# Define variables -HOST_SONAR_URL="http://localhost:9000" # Host's URL to access SonarQube -CONTAINER_SONAR_URL="http://sonarqube:9000" # Container's URL to access SonarQube -PROJECT_KEY="test_key" -PROJECT_NAME="test_project" +#!/bin/bash + +# Display a well-formatted introductory text +echo -e "๐Ÿ”ง Welcome to the SonarQube & CodeCharta Automation Script ๐Ÿ”ง" +echo -e "------------------------------------------------------------" +echo -e "This script automates the process of:" +echo -e "1. Setting up a SonarQube project and resetting the default 'admin' password if needed." +echo -e "2. Running SonarScanner to analyze your project's source code." +echo -e "3. Conducting a CodeCharta analysis of the scanned data." +echo -e "\nYou can choose to provide custom values for the project configuration or use the defaults." +echo -e "To skip the prompts and use all default values, run the script with the -s flag." +echo -e "If the default 'admin' password is still in use, the script will change it to the new password you provide." +echo -e "Note: This is only relevant for users who do not already have an instance of SonarQube running." +echo -e "------------------------------------------------------------\n" + +# Check for skip prompt flag +SKIP_PROMPT=false +while getopts ":s" opt; do + case ${opt} in + s ) + SKIP_PROMPT=true + ;; + \? ) + echo "Invalid option: -$OPTARG" 1>&2 + exit 1 + ;; + esac +done + +# Other variables with default values +HOST_SONAR_URL="http://localhost:9000" # URL used by the host machine to access the SonarQube server +CONTAINER_SONAR_URL="http://sonarqube:9000" # URL used by other Docker containers to access the SonarQube server DEFAULT_SONAR_USER="admin" DEFAULT_SONAR_PASSWORD="admin" -NEW_SONAR_PASSWORD="newadminpassword" # Define the new password you want to set -SONARQUBE_TOKEN_NAME="codecharta_token" # Name of the SonarQube token -SONARQUBE_TOKEN="" # Token generated for SonarScanner (optional, will generate a new one if empty) -PROJECT_BASEDIR="$(pwd)/visualization" # Directory to be scanned -OUTPUT_PATH="$(pwd)/output" # Directory for the output +SONARQUBE_TOKEN_NAME="codecharta_token" +SONARQUBE_TOKEN="" +OUTPUT_PATH="$(pwd)/output" NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" RUN_PROJECT_CLEANUP=true # Set to true to delete the existing SonarQube project -RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks -RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner -WAIT_TIME=60 # Time in seconds to wait after running SonarScanner +RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks +RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner +WAIT_TIME=60 # Time in seconds to wait after running SonarScanner + +# If skip prompt mode is not enabled, prompt for important variables with defaults +if [ "$SKIP_PROMPT" = false ]; then + # PROJECT_KEY: A unique identifier for the project in SonarQube. + # Default is set to 'test_key'. + read -p "๐Ÿ”‘ Enter the Project Key (default: test_key): " PROJECT_KEY + PROJECT_KEY=${PROJECT_KEY:-test_key} + + # PROJECT_NAME: The name of the project in SonarQube. + # Default is set to 'test_project'. + read -p "๐Ÿ“› Enter the Project Name (default: test_project): " PROJECT_NAME + PROJECT_NAME=${PROJECT_NAME:-test_project} + + # NEW_SONAR_PASSWORD: The new password to set for the SonarQube admin user. + # If the default 'admin' password is still in use, the script will change it to this value. + # Default is set to 'newadminpassword'. + read -p "๐Ÿ”’ Enter the new password for the SonarQube admin user (default: newadminpassword): " NEW_SONAR_PASSWORD + NEW_SONAR_PASSWORD=${NEW_SONAR_PASSWORD:-newadminpassword} + + # PROJECT_BASEDIR: The directory containing the source code to be analyzed. + # Default is the 'visualization' directory within the current working directory. + read -p "๐Ÿ“ Enter the directory path to be scanned (default: $(pwd)/visualization): " PROJECT_BASEDIR + PROJECT_BASEDIR=${PROJECT_BASEDIR:-$(pwd)/visualization} +else + # Default values if skip prompt is enabled + PROJECT_KEY="test_key" + PROJECT_NAME="test_project" + NEW_SONAR_PASSWORD="newadminpassword" + PROJECT_BASEDIR="$(pwd)/visualization" +fi # Run the steps ensure_sonarqube_running From 650a69be01f7773c914e3ebe940cb5aba23f7ab5 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 14 Aug 2024 16:13:30 +0200 Subject: [PATCH 13/40] fix: url not being encoded correctly --- script/helpers.sh | 11 +++++++++-- script/run_analysis.sh | 23 +++++++++++++---------- script/sonarqube_management.sh | 10 +++++----- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/script/helpers.sh b/script/helpers.sh index 20132c0079..1bb599a5c9 100644 --- a/script/helpers.sh +++ b/script/helpers.sh @@ -117,6 +117,13 @@ generate_token() { fi echo "โœ… Token generated: $token" - echo "Token response:" - echo "$response" | jq '.' + # echo "Token response:" + # echo "$response" | jq '.' } + +urlencode() { + local raw_str="$1" + local encoded_str + encoded_str=$(jq -rn --arg v "$raw_str" '$v|@uri') + echo "$encoded_str" +} \ No newline at end of file diff --git a/script/run_analysis.sh b/script/run_analysis.sh index 6fb5e93e87..d4df5d9991 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -51,21 +51,21 @@ NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" RUN_PROJECT_CLEANUP=true # Set to true to delete the existing SonarQube project -RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner -WAIT_TIME=60 # Time in seconds to wait after running SonarScanner +RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks +WAIT_TIME=120 # Time in seconds to wait after running SonarScanner # If skip prompt mode is not enabled, prompt for important variables with defaults if [ "$SKIP_PROMPT" = false ]; then # PROJECT_KEY: A unique identifier for the project in SonarQube. - # Default is set to 'test_key'. - read -p "๐Ÿ”‘ Enter the Project Key (default: test_key): " PROJECT_KEY - PROJECT_KEY=${PROJECT_KEY:-test_key} + # Default is set to 'maibornwolff-gmbh_codecharta_visualization'. + read -p "๐Ÿ”‘ Enter the Project Key (default: maibornwolff-gmbh_codecharta_visualization): " PROJECT_KEY + PROJECT_KEY=${PROJECT_KEY:-maibornwolff-gmbh_codecharta_visualization} # PROJECT_NAME: The name of the project in SonarQube. - # Default is set to 'test_project'. - read -p "๐Ÿ“› Enter the Project Name (default: test_project): " PROJECT_NAME - PROJECT_NAME=${PROJECT_NAME:-test_project} + # Default is set to 'CodeCharta Visualization'. + read -p "๐Ÿ“› Enter the Project Name (default: CodeCharta Visualization): " PROJECT_NAME + PROJECT_NAME=${PROJECT_NAME:-CodeCharta Visualization} # NEW_SONAR_PASSWORD: The new password to set for the SonarQube admin user. # If the default 'admin' password is still in use, the script will change it to this value. @@ -79,11 +79,14 @@ if [ "$SKIP_PROMPT" = false ]; then PROJECT_BASEDIR=${PROJECT_BASEDIR:-$(pwd)/visualization} else # Default values if skip prompt is enabled - PROJECT_KEY="test_key" - PROJECT_NAME="test_project" + PROJECT_KEY="maibornwolff-gmbh_codecharta_visualization" + PROJECT_NAME="CodeCharta Visualization" NEW_SONAR_PASSWORD="newadminpassword" PROJECT_BASEDIR="$(pwd)/visualization" fi +# URL-encode PROJECT_KEY and PROJECT_NAME +ENCODED_PROJECT_KEY=$(urlencode "$PROJECT_KEY") +ENCODED_PROJECT_NAME=$(urlencode "$PROJECT_NAME") # Run the steps ensure_sonarqube_running diff --git a/script/sonarqube_management.sh b/script/sonarqube_management.sh index 364be90338..a678d834d2 100644 --- a/script/sonarqube_management.sh +++ b/script/sonarqube_management.sh @@ -43,7 +43,7 @@ ensure_sonarqube_running() { # Wait for SonarQube to be ready only after a new container is created echo "โณ Waiting for SonarQube to be ready..." - sleep 60 # Adjust this sleep time as needed to allow SonarQube to fully start + sleep 120 # Adjust this sleep time as needed to allow SonarQube to fully start } reset_sonarqube_password() { @@ -116,7 +116,7 @@ cleanup_previous_project() { echo "๐Ÿงน Cleaning up previous SonarQube project..." # Delete project - response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/delete?project=$PROJECT_KEY") + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/delete?project=$ENCODED_PROJECT_KEY") http_status=$(echo "$response" | tail -n1) response_body=$(echo "$response" | head -n-1) if [ "$http_status" -eq 404 ]; then @@ -170,7 +170,7 @@ create_sonarqube_project() { # If the project does not exist, create it echo "๐Ÿš€ Creating project in SonarQube..." - response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/create?project=$PROJECT_KEY&name=$PROJECT_NAME") + response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/create?project=$ENCODED_PROJECT_KEY&name=$ENCODED_PROJECT_NAME") http_status=$(echo "$response" | tail -n1) response_body=$(echo "$response" | head -n-1) @@ -182,6 +182,6 @@ create_sonarqube_project() { check_response $http_status "$response_body" "Project creation failed." echo "โœ… Project created successfully." - echo "Project creation response:" - echo "$response_body" | jq '.' + # echo "Project creation response:" + # echo "$response_body" | jq '.' } From 5ccd12e7f493df9792a7d76ca3ea03acc6f57ab8 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 14 Aug 2024 16:37:36 +0200 Subject: [PATCH 14/40] chore: Update SonarQube and SonarScanner analysis script --- script/run_analysis.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/script/run_analysis.sh b/script/run_analysis.sh index d4df5d9991..b61a7bacd1 100644 --- a/script/run_analysis.sh +++ b/script/run_analysis.sh @@ -12,7 +12,6 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi #!/bin/bash -# Display a well-formatted introductory text echo -e "๐Ÿ”ง Welcome to the SonarQube & CodeCharta Automation Script ๐Ÿ”ง" echo -e "------------------------------------------------------------" echo -e "This script automates the process of:" @@ -52,8 +51,8 @@ SONAR_CONTAINER_NAME="sonarqube" RUN_PROJECT_CLEANUP=true # Set to true to delete the existing SonarQube project RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner -RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks -WAIT_TIME=120 # Time in seconds to wait after running SonarScanner +RUN_FINAL_CLEANUP=true # Set to true to perform final cleanup of Docker containers and networks +WAIT_TIME=60 # Time in seconds to wait after running SonarScanner # If skip prompt mode is not enabled, prompt for important variables with defaults if [ "$SKIP_PROMPT" = false ]; then From 99e3a7ab52e78f4883036c5d6d6b768ded2f211a Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 28 Aug 2024 12:56:51 +0200 Subject: [PATCH 15/40] chore: Update SonarQube and SonarScanner analysis script --- script/analysis.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/script/analysis.sh b/script/analysis.sh index 278d80f14f..2bb540a14d 100644 --- a/script/analysis.sh +++ b/script/analysis.sh @@ -29,16 +29,12 @@ run_codecharta_analysis() { -v "$PROJECT_BASEDIR:$PROJECT_BASEDIR" \ -w "$PROJECT_BASEDIR" \ codecharta/codecharta-analysis \ - ccsh sonarimport "$CONTAINER_SONAR_URL" "$PROJECT_KEY" "--user-token=$token" "--output-file=sonar.cc.json" "--merge-modules=false" + ccsh sonarimport "$CONTAINER_SONAR_URL" "$PROJECT_KEY" "--user-token=$token" "--output-file=$PROJECT_BASEDIR/sonar.cc.json" "--merge-modules=false" if [ $? -ne 0 ]; then echo "โŒ CodeCharta analysis failed." exit 1 fi - echo "โœ… CodeCharta analysis complete. Output stored in $OUTPUT_PATH" - - # List the contents of the output directory for verification - echo "Contents of $OUTPUT_PATH:" - ls -l "$OUTPUT_PATH" + echo "โœ… CodeCharta analysis complete. Output stored in $PROJECT_BASEDIR/sonar.cc.json.gz" } From f9c40299374cbb238a8c5b3da18ab0517e6033ce Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Thu, 29 Aug 2024 11:00:26 +0200 Subject: [PATCH 16/40] rafactor: Rafactor folder structure --- .../analysis.sh | 0 .../{ => automated_sonar_analysis}/cleanup.sh | 0 .../dependency_checker.sh | 0 .../{ => automated_sonar_analysis}/helpers.sh | 0 .../run_analysis.sh | 4 +- .../sonarqube_management.sh | 0 visualization/sonar.cc.json/sonar.cc.json | 685 ------------------ 7 files changed, 2 insertions(+), 687 deletions(-) rename script/{ => automated_sonar_analysis}/analysis.sh (100%) rename script/{ => automated_sonar_analysis}/cleanup.sh (100%) rename script/{ => automated_sonar_analysis}/dependency_checker.sh (100%) rename script/{ => automated_sonar_analysis}/helpers.sh (100%) rename script/{ => automated_sonar_analysis}/run_analysis.sh (96%) rename script/{ => automated_sonar_analysis}/sonarqube_management.sh (100%) delete mode 100644 visualization/sonar.cc.json/sonar.cc.json diff --git a/script/analysis.sh b/script/automated_sonar_analysis/analysis.sh similarity index 100% rename from script/analysis.sh rename to script/automated_sonar_analysis/analysis.sh diff --git a/script/cleanup.sh b/script/automated_sonar_analysis/cleanup.sh similarity index 100% rename from script/cleanup.sh rename to script/automated_sonar_analysis/cleanup.sh diff --git a/script/dependency_checker.sh b/script/automated_sonar_analysis/dependency_checker.sh similarity index 100% rename from script/dependency_checker.sh rename to script/automated_sonar_analysis/dependency_checker.sh diff --git a/script/helpers.sh b/script/automated_sonar_analysis/helpers.sh similarity index 100% rename from script/helpers.sh rename to script/automated_sonar_analysis/helpers.sh diff --git a/script/run_analysis.sh b/script/automated_sonar_analysis/run_analysis.sh similarity index 96% rename from script/run_analysis.sh rename to script/automated_sonar_analysis/run_analysis.sh index b61a7bacd1..61ddcd7556 100644 --- a/script/run_analysis.sh +++ b/script/automated_sonar_analysis/run_analysis.sh @@ -49,9 +49,9 @@ OUTPUT_PATH="$(pwd)/output" NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" -RUN_PROJECT_CLEANUP=true # Set to true to delete the existing SonarQube project +RUN_PROJECT_CLEANUP=false # Set to true to delete the existing SonarQube project RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner -RUN_FINAL_CLEANUP=true # Set to true to perform final cleanup of Docker containers and networks +RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks WAIT_TIME=60 # Time in seconds to wait after running SonarScanner # If skip prompt mode is not enabled, prompt for important variables with defaults diff --git a/script/sonarqube_management.sh b/script/automated_sonar_analysis/sonarqube_management.sh similarity index 100% rename from script/sonarqube_management.sh rename to script/automated_sonar_analysis/sonarqube_management.sh diff --git a/visualization/sonar.cc.json/sonar.cc.json b/visualization/sonar.cc.json/sonar.cc.json deleted file mode 100644 index b6b04ed096..0000000000 --- a/visualization/sonar.cc.json/sonar.cc.json +++ /dev/null @@ -1,685 +0,0 @@ -{ - "data": { - "projectName": "", - "nodes": [{ "name": "root", "type": "Folder", "attributes": {}, "link": "", "children": [] }], - "apiVersion": "1.3", - "edges": [], - "attributeTypes": {}, - "attributeDescriptors": { - "sonar_accepted_issues": { - "title": "Accepted Issues", - "description": "Accepted issues", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_blocker_violations": { - "title": "Blocker Violations", - "description": "Total count of issues of the severity blocker", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_bugs": { - "title": "Number of Bugs", - "description": "Number of bug issues", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_class_complexity": { - "title": "Cyclic Class Complexity", - "description": "Maximum cyclomatic complexity based on paths through a class by McCabe", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_classes": { - "title": "Number of Classes", - "description": "Number of classes (including nested classes, interfaces, enums and annotations", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_code_smells": { - "title": "Code Smells", - "description": "Total count of code smell issues", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_cognitive_complexity": { - "title": "Cognitive Complexity", - "description": "How hard is it to understand the code\u0027s control flow", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://www.sonarsource.com/resources/cognitive-complexity/", - "direction": -1 - }, - "comment_lines": { - "title": "Comment Lines", - "description": "Number of lines containing either a comment or commented-out code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_comment_lines_density": { - "title": "Comment Density", - "description": "Density of comment lines in relation to total lines of code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "mcc": { - "title": "Maximum Cyclomatic Complexity", - "description": "Maximum cyclic complexity based on paths through the code by McCabe", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_complexity_in_classes": { - "title": "Class Complexity", - "description": "Maximum cyclomatic complexity based on paths through a class by McCabe", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_complexity_in_functions": { - "title": "Function Complexity", - "description": "Maximum cyclomatic complexity based on paths through a function by McCabe", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_conditions_to_cover": { - "title": "Conditions to Cover", - "description": "Number of conditions which could be covered by unit tests", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_confirmed_issues": { - "title": "Confirmed Issues", - "description": "Total count of issues in the confirmed state", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_critical_violations": { - "title": "Critical Violations", - "description": "Total count of issues of the severity critical", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_duplicated_blocks": { - "title": "Duplicated Blocks", - "description": "Number of duplicated blocks of lines", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_duplicated_files": { - "title": "Duplicated Files", - "description": "Number of files involved in duplications", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_duplicated_lines": { - "title": "Duplicated Lines", - "description": "Number of lines involved in duplications", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_duplicated_lines_density": { - "title": "Duplicated Line Density", - "description": "Density of duplicated lines", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_false_positive_issues": { - "title": "False Positive Issues", - "description": "Total count of issues marked false positive", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_file_complexity": { - "title": "File Complexity", - "description": "Maximum cyclomatic complexity based on paths through a file by McCabe", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_files": { - "title": "Number of Files", - "description": "Number of files", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "average_function_mcc": { - "title": "Function Complexity", - "description": "Maximum cyclomatic complexity based on paths through a function by McCabe", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "functions": { - "title": "Number of Functions", - "description": "Number of functions", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_generated_lines": { - "title": "Generated Lines", - "description": "Number of generated lines of code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_generated_ncloc": { - "title": "Generated Real Lines of Code", - "description": "Number of generated non-empty lines of code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_high_impact_accepted_issues": { - "title": "High Impact Accepted Issues", - "description": "Accepted issues with high impact", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_info_violations": { - "title": "Info Violations", - "description": "Total count of issues of the severity info", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "loc": { - "title": "Number of Lines", - "description": "Number of physical lines (number of carriage returns)", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_lines_to_cover": { - "title": "Lines to Cover", - "description": "Number of lines of code which could be covered by unit tests", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_major_violations": { - "title": "Major Violations", - "description": "Total count of issues of the severity major", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_minor_violations": { - "title": "Minor Violations", - "description": "Total count of issues of the severity minor", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "rloc": { - "title": "Real Lines of Code", - "description": "Number of physical lines that contain at least one character which is neither a whitespace nor a tabulation nor part of a comment", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_accepted_issues": { - "title": "New Accepted Issues", - "description": "New accepted issues", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_blocker_violations": { - "title": "New Blocker Violations", - "description": "Number of issues of the severity blocker raised for the first time in the new code period", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_bugs": { - "title": "Number of New Bugs", - "description": "Number of new bug issues", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_code_smells": { - "title": "New Code Smells", - "description": "Total count of code smell issues raised for the first time in the new code period", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_conditions_to_cover": { - "title": "New Conditions to Cover", - "description": "Number of new/updated conditions which could be covered by unit tests", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_critical_violations": { - "title": "New Critical Violations", - "description": "Number of issues of the severity critical raised for the first time in the new code period", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_development_cost": { - "title": "New Development Cost", - "description": "Development cost of new/updated code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_duplicated_blocks": { - "title": "New Duplicated Blocks", - "description": "Number of duplicated blocks of lines in new/updated code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_duplicated_lines": { - "title": "New Duplicated Lines", - "description": "Number of lines involved in duplications in new/updated code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_duplicated_lines_density": { - "title": "New Duplicated Lines Density", - "description": "Density of duplicated lines in new/updated code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_info_violations": { - "title": "New Info Violations", - "description": "Number of issues of the severity info raised for the first time in the new code period", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_lines": { - "title": "Number of New Lines", - "description": "Number of new/updated lines of code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_lines_to_cover": { - "title": "New Lines to Cover", - "description": "Number of new/updated lines of code which could be covered by unit tests", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_major_violations": { - "title": "New Major Violations", - "description": "Number of issues of the severity major raised for the first time in the new code period", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_minor_violations": { - "title": "New Minor Violations", - "description": "Number of issues of the severity minor raised for the first time in the new code period", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_security_hotspots": { - "title": "New Security Hotspots", - "description": "Number of new security hotspots in the new code period", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_security_hotspots_reviewed_status": { - "title": "New Security Hotspots Reviewed Status", - "description": "Total number of reviewed security hotspots in new code period", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_security_hotspots_to_review_status": { - "title": "New Security Hotspots to Review Status", - "description": "Number of security hotspots to review in new code period", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_sqale_debt_ratio": { - "title": "New SQale Debt Ratio", - "description": "Ratio between the cost to develop the software and the cost to fix it in new/updated code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_uncovered_conditions": { - "title": "New Uncovered Conditions", - "description": "Total number of uncovered conditions in new/updated code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_uncovered_lines": { - "title": "New Uncovered Lines", - "description": "Total number of uncovered lines in new/updated code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_violations": { - "title": "New Violations", - "description": "Number of issues raised for the first time in the new code period", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_new_vulnerabilities": { - "title": "New Vulnerabilities", - "description": "Number of new vulnerability issues", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_open_issues": { - "title": "Number of Open Issues", - "description": "Total count of issues in the open state", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_projects": { - "title": "Number of Projects", - "description": "Total number of projects", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "public_api": { - "title": "Public API", - "description": "Public api available", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_public_undocumented_api": { - "title": "Public Undocumented API", - "description": "Undocumented api available", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_reopened_issues": { - "title": "Number of Reopened Issues", - "description": "Total count of issues in the reopened state", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_security_hotspots": { - "title": "Security Hotspots", - "description": "Number of security hotspots", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_security_hotspots_reviewed_status": { - "title": "Security Hotspots Reviewed Status", - "description": "Total number of reviewed security hotspots", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_security_hotspots_to_review_status": { - "title": "Security Hotspots to Review Status", - "description": "Number of security hotspots to review", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_skipped_tests": { - "title": "Number of skipped Tests", - "description": "Number of skipped unit tests", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_sqale_debt_ratio": { - "title": "SQale Debt Ratio", - "description": "Ratio between the cost to develop the software and the cost to fix it", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "statements": { - "title": "Number of Statements", - "description": "Number of statements", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_test_errors": { - "title": "Number of Test Errors", - "description": "Number of unit tests that have failed", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_test_failures": { - "title": "Number of Test Failures", - "description": "Number of unit tests that have failed with an unexpected exception", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_uncovered_conditions": { - "title": "Uncovered Conditions", - "description": "Total number of uncovered conditions", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_uncovered_lines": { - "title": "Uncovered Lines", - "description": "Total number of uncovered lines", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_violations": { - "title": "Number of Violations", - "description": "Total count of issues in all states", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "sonar_vulnerabilities": { - "title": "Number of Vulnerabilities", - "description": "Number of vulnerability issues", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": -1 - }, - "branch_coverage": { - "title": "Branch Coverage", - "description": "Density of fully covered boolean conditions in flow control structures", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - }, - "sonar_coverage": { - "title": "Coverage", - "description": "Mix of branch and line coverage", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - }, - "line_coverage": { - "title": "Line Coverage", - "description": "Density of fully covered lines of code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - }, - "sonar_new_branch_coverage": { - "title": "New Branch Coverage", - "description": "Density of fully covered boolean conditions in flow control structures in new or updated code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - }, - "sonar_new_coverage": { - "title": "New Coverage", - "description": "Mix of branch and line coverage on new/updated code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - }, - "sonar_new_line_coverage": { - "title": "New Line Coverage", - "description": "Density of fully covered lines of new/updated code", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - }, - "sonar_public_documented_api_density": { - "title": "Public Documented API Density", - "description": "Documented public api available", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - }, - "sonar_pull_request_fixed_issues": { - "title": "Pull request fixed issues", - "description": "Count of issues that would be fixed by the pull request", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - }, - "sonar_security_hotspots_reviewed": { - "title": "Security Hotspots Reviewed", - "description": "Percentage of reviewed (fixed or safe) security hotspots", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - }, - "sonar_test_success_density": { - "title": "Test Success Density", - "description": "Ratio between successful tests and all tests", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - }, - "sonar_tests": { - "title": "Number of Tests", - "description": "Number of unit tests", - "hintLowValue": "", - "hintHighValue": "", - "link": "https://docs.sonarcloud.io/digging-deeper/metric-definitions/", - "direction": 1 - } - }, - "blacklist": [] - }, - "checksum": "6dbbc2977053a514edf15510aa35f34e" -} From 48cdde63df8a2a9aafe4b6dd6952effbc6f641d2 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Mon, 2 Sep 2024 10:43:33 +0200 Subject: [PATCH 17/40] chore: Add release automation script for Linux and macOS --- .../workflows/release-automation-script.yml | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/release-automation-script.yml diff --git a/.github/workflows/release-automation-script.yml b/.github/workflows/release-automation-script.yml new file mode 100644 index 0000000000..f2156204b4 --- /dev/null +++ b/.github/workflows/release-automation-script.yml @@ -0,0 +1,61 @@ +name: Release Automation Script + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + build-linux: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies for Linux + run: | + sudo apt-get update + sudo apt-get install -y shc zip + + - name: Compile the script for Linux + run: | + shc -f automated_sonar_analysis/sonar_analysis.sh -o automated_sonar_analysis/sonar_analysis_linux + + - name: Create a ZIP archive of the script + run: | + zip -r automated_sonar_analysis_linux.zip automated_sonar_analysis/ + + - name: Attach assets to the release + id: upload-release-linux + uses: softprops/action-gh-release@v2 + with: + files: | + automated_sonar_analysis_linux.zip + automated_sonar_analysis/sonar_analysis_linux + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-macos: + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies for macOS + run: | + brew install shc + + - name: Compile the script for macOS + run: | + shc -f automated_sonar_analysis/sonar_analysis.sh -o automated_sonar_analysis/sonar_analysis_macos + + - name: Attach macOS executable to the release + id: upload-release-macos + uses: softprops/action-gh-release@v2 + with: + files: | + automated_sonar_analysis/sonar_analysis_macos + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b7e7ef39384f872975fb5b6b0346887bda89e5cf Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Mon, 2 Sep 2024 11:02:25 +0200 Subject: [PATCH 18/40] chore: Add push trigger to release automation script --- .github/workflows/release-automation-script.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-automation-script.yml b/.github/workflows/release-automation-script.yml index f2156204b4..bf5c07a424 100644 --- a/.github/workflows/release-automation-script.yml +++ b/.github/workflows/release-automation-script.yml @@ -4,6 +4,7 @@ on: release: types: [published] workflow_dispatch: + push: jobs: build-linux: From 4a1d852b5aeb846423ef1d825309e79d5621a6f7 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Mon, 2 Sep 2024 11:12:14 +0200 Subject: [PATCH 19/40] chore: Update release automation script to include source code in ZIP archive --- .../workflows/release-automation-script.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release-automation-script.yml b/.github/workflows/release-automation-script.yml index bf5c07a424..2d5f905d4c 100644 --- a/.github/workflows/release-automation-script.yml +++ b/.github/workflows/release-automation-script.yml @@ -5,7 +5,6 @@ on: types: [published] workflow_dispatch: push: - jobs: build-linux: runs-on: ubuntu-latest @@ -21,19 +20,20 @@ jobs: - name: Compile the script for Linux run: | - shc -f automated_sonar_analysis/sonar_analysis.sh -o automated_sonar_analysis/sonar_analysis_linux + shc -f script/automated_sonar_analysis/sonar_analysis.sh -o script/automated_sonar_analysis/sonar_analysis_linux - - name: Create a ZIP archive of the script + - name: Create a ZIP archive of the source code run: | - zip -r automated_sonar_analysis_linux.zip automated_sonar_analysis/ + cd script/automated_sonar_analysis + zip -r automated_sonar_analysis_source.zip ./* - name: Attach assets to the release id: upload-release-linux uses: softprops/action-gh-release@v2 with: files: | - automated_sonar_analysis_linux.zip - automated_sonar_analysis/sonar_analysis_linux + script/automated_sonar_analysis/automated_sonar_analysis_source.zip + script/automated_sonar_analysis/sonar_analysis_linux env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -50,13 +50,13 @@ jobs: - name: Compile the script for macOS run: | - shc -f automated_sonar_analysis/sonar_analysis.sh -o automated_sonar_analysis/sonar_analysis_macos + shc -f script/automated_sonar_analysis/sonar_analysis.sh -o script/automated_sonar_analysis/sonar_analysis_macos - name: Attach macOS executable to the release id: upload-release-macos uses: softprops/action-gh-release@v2 with: files: | - automated_sonar_analysis/sonar_analysis_macos + script/automated_sonar_analysis/sonar_analysis_macos env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From ca47198d66ccad6d5879a0c8618fe784bd826831 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Mon, 2 Sep 2024 11:15:27 +0200 Subject: [PATCH 20/40] chore: Update SonarQube and SonarScanner analysis script --- script/automated_sonar_analysis/run_analysis.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/script/automated_sonar_analysis/run_analysis.sh b/script/automated_sonar_analysis/run_analysis.sh index 61ddcd7556..93519c52aa 100644 --- a/script/automated_sonar_analysis/run_analysis.sh +++ b/script/automated_sonar_analysis/run_analysis.sh @@ -49,9 +49,9 @@ OUTPUT_PATH="$(pwd)/output" NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" -RUN_PROJECT_CLEANUP=false # Set to true to delete the existing SonarQube project +RUN_PROJECT_CLEANUP=true # Set to true to delete the existing SonarQube project RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner -RUN_FINAL_CLEANUP=false # Set to true to perform final cleanup of Docker containers and networks +RUN_FINAL_CLEANUP=true # Set to true to perform final cleanup of Docker containers and networks WAIT_TIME=60 # Time in seconds to wait after running SonarScanner # If skip prompt mode is not enabled, prompt for important variables with defaults @@ -74,14 +74,14 @@ if [ "$SKIP_PROMPT" = false ]; then # PROJECT_BASEDIR: The directory containing the source code to be analyzed. # Default is the 'visualization' directory within the current working directory. - read -p "๐Ÿ“ Enter the directory path to be scanned (default: $(pwd)/visualization): " PROJECT_BASEDIR - PROJECT_BASEDIR=${PROJECT_BASEDIR:-$(pwd)/visualization} + read -p "๐Ÿ“ Enter the directory path to be scanned (default: $(pwd)/visualization/app): " PROJECT_BASEDIR + PROJECT_BASEDIR=${PROJECT_BASEDIR:-$(pwd)/visualization/app} else # Default values if skip prompt is enabled PROJECT_KEY="maibornwolff-gmbh_codecharta_visualization" PROJECT_NAME="CodeCharta Visualization" NEW_SONAR_PASSWORD="newadminpassword" - PROJECT_BASEDIR="$(pwd)/visualization" + PROJECT_BASEDIR="$(pwd)/visualization/app" fi # URL-encode PROJECT_KEY and PROJECT_NAME ENCODED_PROJECT_KEY=$(urlencode "$PROJECT_KEY") From ba6474a8646aae370547afe792fc3b1bb01bb6c2 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Mon, 2 Sep 2024 14:16:37 +0200 Subject: [PATCH 21/40] chore: prompt the user for installing --- .../{analysis.sh => analysers.sh} | 0 .../dependency_checker.sh | 71 ++++++++++++++++--- .../automated_sonar_analysis/run_analysis.sh | 63 ++++++++-------- 3 files changed, 95 insertions(+), 39 deletions(-) rename script/automated_sonar_analysis/{analysis.sh => analysers.sh} (100%) diff --git a/script/automated_sonar_analysis/analysis.sh b/script/automated_sonar_analysis/analysers.sh similarity index 100% rename from script/automated_sonar_analysis/analysis.sh rename to script/automated_sonar_analysis/analysers.sh diff --git a/script/automated_sonar_analysis/dependency_checker.sh b/script/automated_sonar_analysis/dependency_checker.sh index 98ea2aa367..e4c9b50be4 100644 --- a/script/automated_sonar_analysis/dependency_checker.sh +++ b/script/automated_sonar_analysis/dependency_checker.sh @@ -5,17 +5,34 @@ command_exists() { command -v "$1" >/dev/null 2>&1 } +# Function to prompt the user for installation +prompt_install() { + local command="$1" + local install_command="$2" + + echo "โ—๏ธ The following command will be executed to install $command:" + echo "$install_command" + read -p "Do you want to proceed with the installation? [Y/n]: " -n 1 -r + echo # move to a new line + # Default to 'Y' if the user presses Enter + if [[ $REPLY =~ ^[Nn]$ ]]; then + echo "โš ๏ธ Installation of $command was canceled by the user." + exit 1 + else + eval "$install_command" + fi +} + # Function to install jq on Ubuntu/Debian install_jq_ubuntu() { - echo "๐Ÿ”ง Installing jq on Ubuntu/Debian..." - sudo apt-get update - sudo apt-get install -y jq + local install_command="sudo apt-get update && sudo apt-get install -y jq" + prompt_install "jq on Ubuntu/Debian" "$install_command" } # Function to install jq on macOS install_jq_macos() { - echo "๐Ÿ”ง Installing jq on macOS..." - brew install jq + local install_command="brew install jq" + prompt_install "jq on macOS" "$install_command" } # Function to check the platform and install jq accordingly @@ -38,7 +55,7 @@ install_jq() { exit 1 fi else - echo "โš ๏ธ Unsupported OS. Please install jq manually." + echo "โš ๏ธ Unsupported OS." exit 1 fi } @@ -65,8 +82,46 @@ check_docker() { fi } -# Run the checks +# Function to ensure Docker images are pulled +docker_pull_image() { + local image=$1 + if ! docker image inspect "$image" >/dev/null 2>&1; then + echo "๐Ÿ”ง Pulling Docker image: $image..." + docker pull "$image" + if [ $? -ne 0 ]; then + echo "โŒ Failed to pull Docker image: $image." + exit 1 + fi + else + echo "โ„น๏ธ Docker image '$image' is already available locally." + fi +} + +# Function to ensure all necessary Docker images are available +check_docker_images() { + echo "โ—๏ธ The script needs to pull the following Docker images if they are not already available:" + echo " - sonarsource/sonar-scanner-cli" + echo " - codecharta/codecharta-analysis" + echo " - sonarqube:community" + + read -p "โ“ Do you want to proceed with pulling these images? [Y/n]: " -n 1 -r + echo # move to a new line + # Default to 'Y' if the user presses Enter + if [[ $REPLY =~ ^[Nn]$ ]]; then + echo "๐Ÿšซ Image pulling canceled. The script cannot proceed without these images." + exit 1 + else + docker_pull_image "sonarsource/sonar-scanner-cli" + docker_pull_image "codecharta/codecharta-analysis" + docker_pull_image "sonarqube:community" + fi +} + +# Run the checks for jq and Docker check_jq check_docker -echo "๐ŸŽ‰ All dependencies are installed." +# Check for required Docker images +check_docker_images + +echo "๐ŸŽ‰ All dependencies are installed and required Docker images are available." diff --git a/script/automated_sonar_analysis/run_analysis.sh b/script/automated_sonar_analysis/run_analysis.sh index 93519c52aa..32b933df19 100644 --- a/script/automated_sonar_analysis/run_analysis.sh +++ b/script/automated_sonar_analysis/run_analysis.sh @@ -8,10 +8,9 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi . "$DIR/helpers.sh" . "$DIR/cleanup.sh" . "$DIR/sonarqube_management.sh" -. "$DIR/analysis.sh" - -#!/bin/bash +. "$DIR/analysers.sh" +# Introductory message explaining the script's purpose echo -e "๐Ÿ”ง Welcome to the SonarQube & CodeCharta Automation Script ๐Ÿ”ง" echo -e "------------------------------------------------------------" echo -e "This script automates the process of:" @@ -38,6 +37,21 @@ while getopts ":s" opt; do esac done +# Default values for important variables + +# PROJECT_KEY: A unique identifier for the project in SonarQube. +PROJECT_KEY="maibornwolff-gmbh_codecharta_visualization" + +# PROJECT_NAME: The name of the project in SonarQube. +PROJECT_NAME="CodeCharta Visualization" + +# NEW_SONAR_PASSWORD: The new password to set for the SonarQube admin user. +# If the default 'admin' password is still in use, the script will change it to this value. +NEW_SONAR_PASSWORD="newadminpassword" + +# PROJECT_BASEDIR: The directory containing the source code to be analyzed. +PROJECT_BASEDIR="$(pwd)/visualization/app" + # Other variables with default values HOST_SONAR_URL="http://localhost:9000" # URL used by the host machine to access the SonarQube server CONTAINER_SONAR_URL="http://sonarqube:9000" # URL used by other Docker containers to access the SonarQube server @@ -51,38 +65,25 @@ SONAR_CONTAINER_NAME="sonarqube" RUN_PROJECT_CLEANUP=true # Set to true to delete the existing SonarQube project RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner -RUN_FINAL_CLEANUP=true # Set to true to perform final cleanup of Docker containers and networks +RUN_FINAL_CLEANUP=true # Set to true to perform final cleanup of Docker containers and networks WAIT_TIME=60 # Time in seconds to wait after running SonarScanner # If skip prompt mode is not enabled, prompt for important variables with defaults if [ "$SKIP_PROMPT" = false ]; then - # PROJECT_KEY: A unique identifier for the project in SonarQube. - # Default is set to 'maibornwolff-gmbh_codecharta_visualization'. - read -p "๐Ÿ”‘ Enter the Project Key (default: maibornwolff-gmbh_codecharta_visualization): " PROJECT_KEY - PROJECT_KEY=${PROJECT_KEY:-maibornwolff-gmbh_codecharta_visualization} - - # PROJECT_NAME: The name of the project in SonarQube. - # Default is set to 'CodeCharta Visualization'. - read -p "๐Ÿ“› Enter the Project Name (default: CodeCharta Visualization): " PROJECT_NAME - PROJECT_NAME=${PROJECT_NAME:-CodeCharta Visualization} - - # NEW_SONAR_PASSWORD: The new password to set for the SonarQube admin user. - # If the default 'admin' password is still in use, the script will change it to this value. - # Default is set to 'newadminpassword'. - read -p "๐Ÿ”’ Enter the new password for the SonarQube admin user (default: newadminpassword): " NEW_SONAR_PASSWORD - NEW_SONAR_PASSWORD=${NEW_SONAR_PASSWORD:-newadminpassword} - - # PROJECT_BASEDIR: The directory containing the source code to be analyzed. - # Default is the 'visualization' directory within the current working directory. - read -p "๐Ÿ“ Enter the directory path to be scanned (default: $(pwd)/visualization/app): " PROJECT_BASEDIR - PROJECT_BASEDIR=${PROJECT_BASEDIR:-$(pwd)/visualization/app} -else - # Default values if skip prompt is enabled - PROJECT_KEY="maibornwolff-gmbh_codecharta_visualization" - PROJECT_NAME="CodeCharta Visualization" - NEW_SONAR_PASSWORD="newadminpassword" - PROJECT_BASEDIR="$(pwd)/visualization/app" + # Allow the user to override the default values if desired + read -p "๐Ÿ”‘ Enter the Project Key (default: $PROJECT_KEY): " input + PROJECT_KEY=${input:-$PROJECT_KEY} + + read -p "๐Ÿ“› Enter the Project Name (default: $PROJECT_NAME): " input + PROJECT_NAME=${input:-$PROJECT_NAME} + + read -p "๐Ÿ”’ Enter the new password for the SonarQube admin user (default: $NEW_SONAR_PASSWORD): " input + NEW_SONAR_PASSWORD=${input:-$NEW_SONAR_PASSWORD} + + read -p "๐Ÿ“ Enter the directory path to be scanned (default: $PROJECT_BASEDIR): " input + PROJECT_BASEDIR=${input:-$PROJECT_BASEDIR} fi + # URL-encode PROJECT_KEY and PROJECT_NAME ENCODED_PROJECT_KEY=$(urlencode "$PROJECT_KEY") ENCODED_PROJECT_NAME=$(urlencode "$PROJECT_NAME") @@ -93,7 +94,7 @@ ensure_sonarqube_running # Conditionally reset password reset_sonarqube_password -# Conditionally clean up previous project +# Conditionally clean up the previous project if $RUN_PROJECT_CLEANUP; then cleanup_previous_project fi From c29c612792a21c75d512a89163754a31403da25e Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Mon, 2 Sep 2024 14:16:53 +0200 Subject: [PATCH 22/40] chore: Update release automation script to use new file names for Linux and macOS --- .github/workflows/release-automation-script.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-automation-script.yml b/.github/workflows/release-automation-script.yml index 2d5f905d4c..256b84b586 100644 --- a/.github/workflows/release-automation-script.yml +++ b/.github/workflows/release-automation-script.yml @@ -20,7 +20,7 @@ jobs: - name: Compile the script for Linux run: | - shc -f script/automated_sonar_analysis/sonar_analysis.sh -o script/automated_sonar_analysis/sonar_analysis_linux + shc -f ./script/automated_sonar_analysis/run_analysis.sh -o ./script/automated_sonar_analysis/run_analysis_linux - name: Create a ZIP archive of the source code run: | @@ -33,7 +33,7 @@ jobs: with: files: | script/automated_sonar_analysis/automated_sonar_analysis_source.zip - script/automated_sonar_analysis/sonar_analysis_linux + script/automated_sonar_analysis/run_analysis_linux env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -50,13 +50,13 @@ jobs: - name: Compile the script for macOS run: | - shc -f script/automated_sonar_analysis/sonar_analysis.sh -o script/automated_sonar_analysis/sonar_analysis_macos + shc -f ./script/automated_sonar_analysis/run_analysis.sh -o ./script/automated_sonar_analysis/run_analysis_macos - name: Attach macOS executable to the release id: upload-release-macos uses: softprops/action-gh-release@v2 with: files: | - script/automated_sonar_analysis/sonar_analysis_macos + script/automated_sonar_analysis/run_analysis_macos env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From dcde497e8fce8852f544cc446026eb7bf51bc245 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Mon, 2 Sep 2024 14:18:04 +0200 Subject: [PATCH 23/40] chore: remove release automation script to use new file names for Linux and macOS --- .../workflows/release-automation-script.yml | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 .github/workflows/release-automation-script.yml diff --git a/.github/workflows/release-automation-script.yml b/.github/workflows/release-automation-script.yml deleted file mode 100644 index 256b84b586..0000000000 --- a/.github/workflows/release-automation-script.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Release Automation Script - -on: - release: - types: [published] - workflow_dispatch: - push: -jobs: - build-linux: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install dependencies for Linux - run: | - sudo apt-get update - sudo apt-get install -y shc zip - - - name: Compile the script for Linux - run: | - shc -f ./script/automated_sonar_analysis/run_analysis.sh -o ./script/automated_sonar_analysis/run_analysis_linux - - - name: Create a ZIP archive of the source code - run: | - cd script/automated_sonar_analysis - zip -r automated_sonar_analysis_source.zip ./* - - - name: Attach assets to the release - id: upload-release-linux - uses: softprops/action-gh-release@v2 - with: - files: | - script/automated_sonar_analysis/automated_sonar_analysis_source.zip - script/automated_sonar_analysis/run_analysis_linux - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - build-macos: - runs-on: macos-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install dependencies for macOS - run: | - brew install shc - - - name: Compile the script for macOS - run: | - shc -f ./script/automated_sonar_analysis/run_analysis.sh -o ./script/automated_sonar_analysis/run_analysis_macos - - - name: Attach macOS executable to the release - id: upload-release-macos - uses: softprops/action-gh-release@v2 - with: - files: | - script/automated_sonar_analysis/run_analysis_macos - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 9c5a049533b94af44a4774aafd852a155cc0572e Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Tue, 3 Sep 2024 16:04:00 +0200 Subject: [PATCH 24/40] fix: fix script not working on macos because of unsupported bash features --- .../sonarqube_management.sh | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/script/automated_sonar_analysis/sonarqube_management.sh b/script/automated_sonar_analysis/sonarqube_management.sh index a678d834d2..69268367c8 100644 --- a/script/automated_sonar_analysis/sonarqube_management.sh +++ b/script/automated_sonar_analysis/sonarqube_management.sh @@ -50,9 +50,9 @@ reset_sonarqube_password() { echo "๐Ÿ” Testing SonarQube credentials: Username='$DEFAULT_SONAR_USER', Password='$DEFAULT_SONAR_PASSWORD'" # Attempt to log in with the default password to check if it's still active - response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") - http_status="${response: -3}" - response_body="${response::-3}" + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n -1) # Check if the response is valid JSON before parsing if echo "$response_body" | jq -e . >/dev/null 2>&1; then @@ -65,9 +65,10 @@ reset_sonarqube_password() { echo "โ„น๏ธ Default credentials are not in use. Checking the new password..." # Attempt to log in with the new password - response=$(curl -u $DEFAULT_SONAR_USER:$NEW_SONAR_PASSWORD -X GET -s -w "%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") - http_status="${response: -3}" - response_body="${response::-3}" + response=$(curl -u $DEFAULT_SONAR_USER:$NEW_SONAR_PASSWORD -X GET -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") + + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n -1) if echo "$response_body" | jq -e . >/dev/null 2>&1; then is_valid=$(echo "$response_body" | jq -r '.valid') @@ -93,12 +94,12 @@ reset_sonarqube_password() { # Function to change the default password to a new password change_default_password() { - response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X POST -s -w "%{http_code}" \ + response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X POST -s -w "\n%{http_code}" \ -d "login=$DEFAULT_SONAR_USER&previousPassword=$DEFAULT_SONAR_PASSWORD&password=$NEW_SONAR_PASSWORD" \ "$HOST_SONAR_URL/api/users/change_password") - http_status="${response: -3}" - response_body="${response::-3}" + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n -1) if [ "$http_status" == "200" ] || [ "$http_status" == "204" ]; then echo "โœ… Password has been successfully changed to the new password." @@ -118,7 +119,7 @@ cleanup_previous_project() { # Delete project response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/delete?project=$ENCODED_PROJECT_KEY") http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n-1) + response_body=$(echo "$response" | head -n -1) if [ "$http_status" -eq 404 ]; then echo "โ„น๏ธ Project not found, skipping deletion." elif [ -z "$http_status" ]; then @@ -137,7 +138,7 @@ revoke_token() { # Revoke token response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/revoke?name=$SONARQUBE_TOKEN_NAME") http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n-1) + response_body=$(echo "$response" | head -n -1) if [ "$http_status" -eq 404 ]; then echo "โ„น๏ธ Token not found, skipping revocation." elif [ -z "$http_status" ]; then @@ -157,7 +158,7 @@ create_sonarqube_project() { # Check if the project already exists response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X GET -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/search?projects=$PROJECT_KEY") http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n-1) + response_body=$(echo "$response" | head -n -1) if [[ "$http_status" -eq 200 && $(echo "$response_body" | jq -r '.components | length') -gt 0 ]]; then echo "โ„น๏ธ Project '$PROJECT_KEY' already exists. Skipping creation." @@ -172,7 +173,7 @@ create_sonarqube_project() { echo "๐Ÿš€ Creating project in SonarQube..." response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/create?project=$ENCODED_PROJECT_KEY&name=$ENCODED_PROJECT_NAME") http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n-1) + response_body=$(echo "$response" | head -n -1) if [[ "$http_status" -eq 404 ]]; then echo "โŒ Failed: Project creation failed. The endpoint may be incorrect or deprecated." From 85d51f8d6e9b1fd90c2596bbe2ef9de2ea53c1da Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Tue, 3 Sep 2024 16:42:23 +0200 Subject: [PATCH 25/40] refactor: fix the project base dir and the sonarscanner arguments --- script/automated_sonar_analysis/analysers.sh | 5 ++--- script/automated_sonar_analysis/run_analysis.sh | 3 +-- script/automated_sonar_analysis/sonarqube_management.sh | 9 ++------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/script/automated_sonar_analysis/analysers.sh b/script/automated_sonar_analysis/analysers.sh index 2bb540a14d..c1af5c08f4 100644 --- a/script/automated_sonar_analysis/analysers.sh +++ b/script/automated_sonar_analysis/analysers.sh @@ -3,13 +3,12 @@ # Run SonarScanner in the container and capture output run_sonarscanner() { echo "๐Ÿ” Running SonarScanner..." - docker run --rm \ + docker run --rm -it \ --network $NETWORK_NAME \ -v "$PROJECT_BASEDIR:/usr/src" \ + -w /usr/src \ sonarsource/sonar-scanner-cli \ sonar-scanner \ - -Dsonar.projectKey=$PROJECT_KEY \ - -Dsonar.sources=/usr/src \ -Dsonar.token=$token \ -Dsonar.host.url="$CONTAINER_SONAR_URL" diff --git a/script/automated_sonar_analysis/run_analysis.sh b/script/automated_sonar_analysis/run_analysis.sh index 32b933df19..ac5dcb1136 100644 --- a/script/automated_sonar_analysis/run_analysis.sh +++ b/script/automated_sonar_analysis/run_analysis.sh @@ -50,7 +50,7 @@ PROJECT_NAME="CodeCharta Visualization" NEW_SONAR_PASSWORD="newadminpassword" # PROJECT_BASEDIR: The directory containing the source code to be analyzed. -PROJECT_BASEDIR="$(pwd)/visualization/app" +PROJECT_BASEDIR="$(cd "$(dirname "$DIR")/../visualization" && pwd)" # Other variables with default values HOST_SONAR_URL="http://localhost:9000" # URL used by the host machine to access the SonarQube server @@ -59,7 +59,6 @@ DEFAULT_SONAR_USER="admin" DEFAULT_SONAR_PASSWORD="admin" SONARQUBE_TOKEN_NAME="codecharta_token" SONARQUBE_TOKEN="" -OUTPUT_PATH="$(pwd)/output" NETWORK_NAME="sonarnet" SONAR_CONTAINER_NAME="sonarqube" diff --git a/script/automated_sonar_analysis/sonarqube_management.sh b/script/automated_sonar_analysis/sonarqube_management.sh index 69268367c8..e33ad3faa1 100644 --- a/script/automated_sonar_analysis/sonarqube_management.sh +++ b/script/automated_sonar_analysis/sonarqube_management.sh @@ -14,7 +14,6 @@ ensure_network_exists() { fi } -# Step 1: Ensure the SonarQube container is running ensure_sonarqube_running() { # Ensure the Docker network exists before running the container ensure_network_exists @@ -42,7 +41,7 @@ ensure_sonarqube_running() { docker run -d --name $SONAR_CONTAINER_NAME --network $NETWORK_NAME -p 9000:9000 sonarqube:community # Wait for SonarQube to be ready only after a new container is created - echo "โณ Waiting for SonarQube to be ready..." + echo "โณ Waiting for 120 seconds to esnure SonarQube is ready..." sleep 120 # Adjust this sleep time as needed to allow SonarQube to fully start } @@ -92,7 +91,6 @@ reset_sonarqube_password() { fi } -# Function to change the default password to a new password change_default_password() { response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X POST -s -w "\n%{http_code}" \ -d "login=$DEFAULT_SONAR_USER&previousPassword=$DEFAULT_SONAR_PASSWORD&password=$NEW_SONAR_PASSWORD" \ @@ -112,7 +110,6 @@ change_default_password() { } -# Cleanup previous SonarQube project cleanup_previous_project() { echo "๐Ÿงน Cleaning up previous SonarQube project..." @@ -131,7 +128,6 @@ cleanup_previous_project() { fi } -# Revoke the existing SonarQube token revoke_token() { echo "๐Ÿงน Revoking existing SonarQube token..." @@ -183,6 +179,5 @@ create_sonarqube_project() { check_response $http_status "$response_body" "Project creation failed." echo "โœ… Project created successfully." - # echo "Project creation response:" - # echo "$response_body" | jq '.' + } From 43eb020a190afcde32fb1295725fae365eeef5c4 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 4 Sep 2024 16:11:53 +0200 Subject: [PATCH 26/40] fix macos not recognising command --- .../sonarqube_management.sh | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/script/automated_sonar_analysis/sonarqube_management.sh b/script/automated_sonar_analysis/sonarqube_management.sh index e33ad3faa1..37e5d44893 100644 --- a/script/automated_sonar_analysis/sonarqube_management.sh +++ b/script/automated_sonar_analysis/sonarqube_management.sh @@ -41,7 +41,7 @@ ensure_sonarqube_running() { docker run -d --name $SONAR_CONTAINER_NAME --network $NETWORK_NAME -p 9000:9000 sonarqube:community # Wait for SonarQube to be ready only after a new container is created - echo "โณ Waiting for 120 seconds to esnure SonarQube is ready..." + echo "โณ Waiting for 120 seconds to ensure SonarQube is ready..." sleep 120 # Adjust this sleep time as needed to allow SonarQube to fully start } @@ -50,8 +50,8 @@ reset_sonarqube_password() { # Attempt to log in with the default password to check if it's still active response=$(curl -u $DEFAULT_SONAR_USER:$DEFAULT_SONAR_PASSWORD -X GET -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") - http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -1) + response_body=$(echo "$response" | head -1) # Check if the response is valid JSON before parsing if echo "$response_body" | jq -e . >/dev/null 2>&1; then @@ -66,8 +66,8 @@ reset_sonarqube_password() { # Attempt to log in with the new password response=$(curl -u $DEFAULT_SONAR_USER:$NEW_SONAR_PASSWORD -X GET -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") - http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -1) + response_body=$(echo "$response" | head -1) if echo "$response_body" | jq -e . >/dev/null 2>&1; then is_valid=$(echo "$response_body" | jq -r '.valid') @@ -96,8 +96,8 @@ change_default_password() { -d "login=$DEFAULT_SONAR_USER&previousPassword=$DEFAULT_SONAR_PASSWORD&password=$NEW_SONAR_PASSWORD" \ "$HOST_SONAR_URL/api/users/change_password") - http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -1) + response_body=$(echo "$response" | head -1) if [ "$http_status" == "200" ] || [ "$http_status" == "204" ]; then echo "โœ… Password has been successfully changed to the new password." @@ -115,8 +115,8 @@ cleanup_previous_project() { # Delete project response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/delete?project=$ENCODED_PROJECT_KEY") - http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -1) + response_body=$(echo "$response" | head -1) if [ "$http_status" -eq 404 ]; then echo "โ„น๏ธ Project not found, skipping deletion." elif [ -z "$http_status" ]; then @@ -133,8 +133,8 @@ revoke_token() { # Revoke token response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/user_tokens/revoke?name=$SONARQUBE_TOKEN_NAME") - http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -1) + response_body=$(echo "$response" | head -1) if [ "$http_status" -eq 404 ]; then echo "โ„น๏ธ Token not found, skipping revocation." elif [ -z "$http_status" ]; then @@ -153,8 +153,8 @@ create_sonarqube_project() { # Check if the project already exists response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X GET -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/search?projects=$PROJECT_KEY") - http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -1) + response_body=$(echo "$response" | head -1) if [[ "$http_status" -eq 200 && $(echo "$response_body" | jq -r '.components | length') -gt 0 ]]; then echo "โ„น๏ธ Project '$PROJECT_KEY' already exists. Skipping creation." @@ -168,8 +168,8 @@ create_sonarqube_project() { # If the project does not exist, create it echo "๐Ÿš€ Creating project in SonarQube..." response=$(curl -u $SONAR_USER:$SONAR_PASSWORD -X POST -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/projects/create?project=$ENCODED_PROJECT_KEY&name=$ENCODED_PROJECT_NAME") - http_status=$(echo "$response" | tail -n1) - response_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -1) + response_body=$(echo "$response" | head -1) if [[ "$http_status" -eq 404 ]]; then echo "โŒ Failed: Project creation failed. The endpoint may be incorrect or deprecated." From 9d280b6ce70efb3c7beec59d112cd868f3014650 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 4 Sep 2024 17:36:50 +0200 Subject: [PATCH 27/40] feat: add dynamic wait --- script/automated_sonar_analysis/analysers.sh | 29 ++++++++ .../dependency_checker.sh | 8 +- .../automated_sonar_analysis/run_analysis.sh | 74 ++++++++++--------- .../sonarqube_management.sh | 43 +++++++++-- 4 files changed, 108 insertions(+), 46 deletions(-) diff --git a/script/automated_sonar_analysis/analysers.sh b/script/automated_sonar_analysis/analysers.sh index c1af5c08f4..2016829785 100644 --- a/script/automated_sonar_analysis/analysers.sh +++ b/script/automated_sonar_analysis/analysers.sh @@ -16,9 +16,38 @@ run_sonarscanner() { echo "โŒ SonarScanner analysis failed." exit 1 fi + echo "โœ… SonarScanner analysis complete." + + interval=2 # Check every 5 seconds + waited=0 + echo "โณ Waiting for the data to be fully uploaded to SonarQube..." + + while true; do + response=$(curl -s -u $SONAR_USER:$SONAR_PASSWORD -w "\n%{http_code}" "$HOST_SONAR_URL/api/ce/component?component=$PROJECT_KEY") + + http_status=$(echo "$response" | tail -1) + response_body=$(echo "$response" | head -1) + + check_response "$http_status" "$response_body" "SonarQube data processing failed." + + status=$(echo "$response_body" | jq -r '.current.status') + + if [ "$status" == "SUCCESS" ]; then + echo -e "\nโœ… Data has been fully uploaded and processed by SonarQube!" + break + elif [ "$waited" -ge "$TIMEOUT_PERIOD" ]; then + echo -e "\nโŒ SonarQube did not finish processing the data within $TIMEOUT_PERIOD seconds." + exit 1 + fi + + echo -n "." + sleep "$interval" + waited=$((waited + interval)) + done } + # Run CodeCharta analysis using docker run run_codecharta_analysis() { echo "๐Ÿ“Š Running CodeCharta analysis..." diff --git a/script/automated_sonar_analysis/dependency_checker.sh b/script/automated_sonar_analysis/dependency_checker.sh index e4c9b50be4..e244d49036 100644 --- a/script/automated_sonar_analysis/dependency_checker.sh +++ b/script/automated_sonar_analysis/dependency_checker.sh @@ -16,7 +16,7 @@ prompt_install() { echo # move to a new line # Default to 'Y' if the user presses Enter if [[ $REPLY =~ ^[Nn]$ ]]; then - echo "โš ๏ธ Installation of $command was canceled by the user." + echo "๐Ÿšจ Installation of $command was canceled by the user." exit 1 else eval "$install_command" @@ -42,7 +42,7 @@ install_jq() { if command_exists apt-get; then install_jq_ubuntu else - echo "โš ๏ธ Unsupported Linux distribution. Please install jq manually." + echo "๐Ÿšจ Unsupported Linux distribution. Please install jq manually." exit 1 fi elif [[ "$OSTYPE" == "darwin"* ]]; then @@ -50,12 +50,12 @@ install_jq() { if command_exists brew; then install_jq_macos else - echo "โš ๏ธ Homebrew is not installed. Please install Homebrew first." + echo "๐Ÿšจ Homebrew is not installed. Please install Homebrew first." echo "๐Ÿ’ป Visit https://brew.sh/ for installation instructions." exit 1 fi else - echo "โš ๏ธ Unsupported OS." + echo "๐Ÿšจ Unsupported OS." exit 1 fi } diff --git a/script/automated_sonar_analysis/run_analysis.sh b/script/automated_sonar_analysis/run_analysis.sh index ac5dcb1136..7f6b036e73 100644 --- a/script/automated_sonar_analysis/run_analysis.sh +++ b/script/automated_sonar_analysis/run_analysis.sh @@ -1,6 +1,7 @@ #!/bin/bash -# Import the necessary helper scripts +### Import helper functions and scripts ####################################### + DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi @@ -10,6 +11,43 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi . "$DIR/sonarqube_management.sh" . "$DIR/analysers.sh" +### Configuration ############################################################# + +# PROJECT_KEY: A unique identifier for the project in SonarQube. +PROJECT_KEY="maibornwolff-gmbh_codecharta_visualization" + +# PROJECT_NAME: The name of the project in SonarQube. +PROJECT_NAME="CodeCharta Visualization" + +# NEW_SONAR_PASSWORD: The new password to set for the SonarQube admin user. +# If the default 'admin' password is still in use, the script will change it to this value. +NEW_SONAR_PASSWORD="newadminpassword" + +# PROJECT_BASEDIR: The directory containing the source code to be analyzed. +PROJECT_BASEDIR="$(cd "$(dirname "$DIR")/../visualization" && pwd)" + +# Other variables with default values +HOST_SONAR_URL="http://localhost:9000" # URL used by the host machine to access the SonarQube server +CONTAINER_SONAR_URL="http://sonarqube:9000" # URL used by other Docker containers to access the SonarQube server +DEFAULT_SONAR_USER="admin" +DEFAULT_SONAR_PASSWORD="admin" +SONARQUBE_TOKEN_NAME="codecharta_token" +SONARQUBE_TOKEN="" +NETWORK_NAME="sonarnet" +SONAR_CONTAINER_NAME="sonarqube" + +# Set to true to delete the existing SonarQube project +RUN_PROJECT_CLEANUP=true +# Set to true to run SonarScanner +RUN_SONAR_SCANNER=true +# Set to true to run the final cleanup of Docker containers and networks +RUN_FINAL_CLEANUP=false +# Timeout period in seconds for waiting on SonarQube data processing and startup +TIMEOUT_PERIOD=10000 + + +### Main Script ############################################################### + # Introductory message explaining the script's purpose echo -e "๐Ÿ”ง Welcome to the SonarQube & CodeCharta Automation Script ๐Ÿ”ง" echo -e "------------------------------------------------------------" @@ -37,36 +75,6 @@ while getopts ":s" opt; do esac done -# Default values for important variables - -# PROJECT_KEY: A unique identifier for the project in SonarQube. -PROJECT_KEY="maibornwolff-gmbh_codecharta_visualization" - -# PROJECT_NAME: The name of the project in SonarQube. -PROJECT_NAME="CodeCharta Visualization" - -# NEW_SONAR_PASSWORD: The new password to set for the SonarQube admin user. -# If the default 'admin' password is still in use, the script will change it to this value. -NEW_SONAR_PASSWORD="newadminpassword" - -# PROJECT_BASEDIR: The directory containing the source code to be analyzed. -PROJECT_BASEDIR="$(cd "$(dirname "$DIR")/../visualization" && pwd)" - -# Other variables with default values -HOST_SONAR_URL="http://localhost:9000" # URL used by the host machine to access the SonarQube server -CONTAINER_SONAR_URL="http://sonarqube:9000" # URL used by other Docker containers to access the SonarQube server -DEFAULT_SONAR_USER="admin" -DEFAULT_SONAR_PASSWORD="admin" -SONARQUBE_TOKEN_NAME="codecharta_token" -SONARQUBE_TOKEN="" -NETWORK_NAME="sonarnet" -SONAR_CONTAINER_NAME="sonarqube" - -RUN_PROJECT_CLEANUP=true # Set to true to delete the existing SonarQube project -RUN_SONAR_SCANNER=true # Set to false to skip running SonarScanner -RUN_FINAL_CLEANUP=true # Set to true to perform final cleanup of Docker containers and networks -WAIT_TIME=60 # Time in seconds to wait after running SonarScanner - # If skip prompt mode is not enabled, prompt for important variables with defaults if [ "$SKIP_PROMPT" = false ]; then # Allow the user to override the default values if desired @@ -108,10 +116,6 @@ generate_token # Conditionally run the SonarScanner if $RUN_SONAR_SCANNER; then run_sonarscanner - - # Wait for the data to be fully uploaded to SonarQube - echo "โณ Waiting for $WAIT_TIME seconds to ensure data is uploaded to SonarQube..." - sleep $WAIT_TIME fi # Run the CodeCharta analysis diff --git a/script/automated_sonar_analysis/sonarqube_management.sh b/script/automated_sonar_analysis/sonarqube_management.sh index 37e5d44893..bd9df3d699 100644 --- a/script/automated_sonar_analysis/sonarqube_management.sh +++ b/script/automated_sonar_analysis/sonarqube_management.sh @@ -15,7 +15,6 @@ ensure_network_exists() { } ensure_sonarqube_running() { - # Ensure the Docker network exists before running the container ensure_network_exists # Check if the SonarQube container is already running with the correct settings @@ -24,25 +23,56 @@ ensure_sonarqube_running() { running_container=$(docker ps -q -f name=$SONAR_CONTAINER_NAME) if [ "$running_container" ]; then echo "โ„น๏ธ SonarQube container is already running." + wait_for_sonarqube_ready return else - echo "โš ๏ธ SonarQube container exists but is not running. Starting it..." + echo "๐Ÿšจ SonarQube container exists but is not running. Starting it..." docker start $SONAR_CONTAINER_NAME if [ $? -ne 0 ]; then echo "โŒ Failed to start existing SonarQube container." exit 1 fi + wait_for_sonarqube_ready return fi fi - # If no container exists, create and start a new one echo "๐Ÿš€ Starting SonarQube container..." docker run -d --name $SONAR_CONTAINER_NAME --network $NETWORK_NAME -p 9000:9000 sonarqube:community - # Wait for SonarQube to be ready only after a new container is created - echo "โณ Waiting for 120 seconds to ensure SonarQube is ready..." - sleep 120 # Adjust this sleep time as needed to allow SonarQube to fully start + wait_for_sonarqube_ready +} + +wait_for_sonarqube_ready() { + echo "โณ Waiting for SonarQube to be ready..." + + local check_interval=2 # Time to wait between each check (in seconds) + local elapsed_time=0 # Track the total elapsed time + local response + local sonarqube_status + + while [ $elapsed_time -lt $TIMEOUT_PERIOD ]; do + response=$(curl -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/system/status") + + http_status=$(echo "$response" | tail -1) + response_body=$(echo "$response" | head -1) + + check_response "$http_status" "$response_body" "Checking SonarQube readiness failed." + + sonarqube_status=$(echo "$response_body" | jq -r '.status') + + if [ "$sonarqube_status" == "UP" ]; then + echo -e "\nโœ… SonarQube is ready!" + return + else + echo -n "." + sleep $check_interval + elapsed_time=$((elapsed_time + check_interval)) + fi + done + + echo "โŒ SonarQube did not become ready within $timeout_period seconds." + exit 1 } reset_sonarqube_password() { @@ -179,5 +209,4 @@ create_sonarqube_project() { check_response $http_status "$response_body" "Project creation failed." echo "โœ… Project created successfully." - } From 46b8f5437ba76dc45392969c3793bf35051c59dd Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 4 Sep 2024 17:43:42 +0200 Subject: [PATCH 28/40] fix: improve dep checker --- .../dependency_checker.sh | 39 +++++++++++++------ script/automated_sonar_analysis/helpers.sh | 4 +- .../sonarqube_management.sh | 12 +++--- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/script/automated_sonar_analysis/dependency_checker.sh b/script/automated_sonar_analysis/dependency_checker.sh index e244d49036..9b36a794b7 100644 --- a/script/automated_sonar_analysis/dependency_checker.sh +++ b/script/automated_sonar_analysis/dependency_checker.sh @@ -93,27 +93,44 @@ docker_pull_image() { exit 1 fi else - echo "โ„น๏ธ Docker image '$image' is already available locally." + echo "โ—๏ธ Docker image '$image' is already available locally." fi } -# Function to ensure all necessary Docker images are available +# Function to check if images exist locally and pull only if needed check_docker_images() { - echo "โ—๏ธ The script needs to pull the following Docker images if they are not already available:" - echo " - sonarsource/sonar-scanner-cli" - echo " - codecharta/codecharta-analysis" - echo " - sonarqube:community" - + local images=("sonarsource/sonar-scanner-cli" "codecharta/codecharta-analysis" "sonarqube:community") + local missing_images=() + + # Check if the required images are present locally + for image in "${images[@]}"; do + if ! docker image inspect "$image" >/dev/null 2>&1; then + missing_images+=("$image") + else + echo "โœ… Docker image '$image' is already available locally." + fi + done + + # If no missing images, skip the prompt + if [ ${#missing_images[@]} -eq 0 ]; then + return + fi + + # If there are missing images, prompt the user to pull them + echo "โ—๏ธ The script needs to pull the following Docker images:" + for img in "${missing_images[@]}"; do + echo " - $img" + done + read -p "โ“ Do you want to proceed with pulling these images? [Y/n]: " -n 1 -r echo # move to a new line - # Default to 'Y' if the user presses Enter if [[ $REPLY =~ ^[Nn]$ ]]; then echo "๐Ÿšซ Image pulling canceled. The script cannot proceed without these images." exit 1 else - docker_pull_image "sonarsource/sonar-scanner-cli" - docker_pull_image "codecharta/codecharta-analysis" - docker_pull_image "sonarqube:community" + for img in "${missing_images[@]}"; do + docker_pull_image "$img" + done fi } diff --git a/script/automated_sonar_analysis/helpers.sh b/script/automated_sonar_analysis/helpers.sh index 1bb599a5c9..81ee75e39f 100644 --- a/script/automated_sonar_analysis/helpers.sh +++ b/script/automated_sonar_analysis/helpers.sh @@ -55,7 +55,7 @@ change_default_password() { exit 1 fi else - echo "โ„น๏ธ Default password does not need to be changed or has already been changed." + echo "โ—๏ธ Default password does not need to be changed or has already been changed." fi } @@ -86,7 +86,7 @@ token_exists() { # Function to generate a token for SonarScanner only if it doesn't already exist generate_token() { if [[ -n "$SONARQUBE_TOKEN" ]]; then - echo "โ„น๏ธ Using predefined token." + echo "โ—๏ธ Using predefined token." token=$SONARQUBE_TOKEN if ! is_valid_token "$token"; then echo "โŒ Predefined token is invalid." diff --git a/script/automated_sonar_analysis/sonarqube_management.sh b/script/automated_sonar_analysis/sonarqube_management.sh index bd9df3d699..5086820566 100644 --- a/script/automated_sonar_analysis/sonarqube_management.sh +++ b/script/automated_sonar_analysis/sonarqube_management.sh @@ -10,7 +10,7 @@ ensure_network_exists() { exit 1 fi else - echo "โ„น๏ธ Docker network $NETWORK_NAME already exists." + echo "โ—๏ธ Docker network $NETWORK_NAME already exists." fi } @@ -22,7 +22,7 @@ ensure_sonarqube_running() { if [ "$existing_container" ]; then running_container=$(docker ps -q -f name=$SONAR_CONTAINER_NAME) if [ "$running_container" ]; then - echo "โ„น๏ธ SonarQube container is already running." + echo "โ—๏ธ SonarQube container is already running." wait_for_sonarqube_ready return else @@ -91,7 +91,7 @@ reset_sonarqube_password() { echo "โœ… Default credentials are valid. Proceeding to change the password..." change_default_password else - echo "โ„น๏ธ Default credentials are not in use. Checking the new password..." + echo "โ—๏ธ Default credentials are not in use. Checking the new password..." # Attempt to log in with the new password response=$(curl -u $DEFAULT_SONAR_USER:$NEW_SONAR_PASSWORD -X GET -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") @@ -148,7 +148,7 @@ cleanup_previous_project() { http_status=$(echo "$response" | tail -1) response_body=$(echo "$response" | head -1) if [ "$http_status" -eq 404 ]; then - echo "โ„น๏ธ Project not found, skipping deletion." + echo "โ—๏ธ Project not found, skipping deletion." elif [ -z "$http_status" ]; then echo "โŒ Failed to connect to SonarQube. Ensure that SonarQube is running and accessible at $HOST_SONAR_URL." exit 1 @@ -166,7 +166,7 @@ revoke_token() { http_status=$(echo "$response" | tail -1) response_body=$(echo "$response" | head -1) if [ "$http_status" -eq 404 ]; then - echo "โ„น๏ธ Token not found, skipping revocation." + echo "โ—๏ธ Token not found, skipping revocation." elif [ -z "$http_status" ]; then echo "โŒ Failed to connect to SonarQube. Ensure that SonarQube is running and accessible at $HOST_SONAR_URL." exit 1 @@ -187,7 +187,7 @@ create_sonarqube_project() { response_body=$(echo "$response" | head -1) if [[ "$http_status" -eq 200 && $(echo "$response_body" | jq -r '.components | length') -gt 0 ]]; then - echo "โ„น๏ธ Project '$PROJECT_KEY' already exists. Skipping creation." + echo "โ—๏ธ Project '$PROJECT_KEY' already exists. Skipping creation." return elif [[ "$http_status" -eq 404 ]]; then echo "โŒ Failed: Unable to check if the project exists. The endpoint may be incorrect or deprecated." From 7c073c4ebdf229d5b894173fd62440f4151e9290 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 4 Sep 2024 18:54:07 +0200 Subject: [PATCH 29/40] feat: add spinners to see when loading --- script/automated_sonar_analysis/analysers.sh | 34 +++++++++++++++---- script/automated_sonar_analysis/helpers.sh | 25 ++++++++++++-- .../sonarqube_management.sh | 27 ++++++++------- 3 files changed, 65 insertions(+), 21 deletions(-) diff --git a/script/automated_sonar_analysis/analysers.sh b/script/automated_sonar_analysis/analysers.sh index 2016829785..048e4bcce2 100644 --- a/script/automated_sonar_analysis/analysers.sh +++ b/script/automated_sonar_analysis/analysers.sh @@ -3,6 +3,11 @@ # Run SonarScanner in the container and capture output run_sonarscanner() { echo "๐Ÿ” Running SonarScanner..." + + # Print start of dimmed output + echo -e "\033[2m" # Start dimming the text + + # Run the Docker container with SonarScanner and display dimmed output docker run --rm -it \ --network $NETWORK_NAME \ -v "$PROJECT_BASEDIR:/usr/src" \ @@ -12,36 +17,47 @@ run_sonarscanner() { -Dsonar.token=$token \ -Dsonar.host.url="$CONTAINER_SONAR_URL" + # Stop dimming after the Docker command completes + echo -e "\033[0m" # Reset to normal text + if [ $? -ne 0 ]; then echo "โŒ SonarScanner analysis failed." exit 1 fi echo "โœ… SonarScanner analysis complete." - - interval=2 # Check every 5 seconds + + wait_for_data_processing +} + +wait_for_data_processing() { + start_spinner "โณ Waiting for the data to be fully uploaded to SonarQube..." & + spinner_pid=$! + + interval=2 # Check every 2 seconds waited=0 - echo "โณ Waiting for the data to be fully uploaded to SonarQube..." while true; do response=$(curl -s -u $SONAR_USER:$SONAR_PASSWORD -w "\n%{http_code}" "$HOST_SONAR_URL/api/ce/component?component=$PROJECT_KEY") - http_status=$(echo "$response" | tail -1) - response_body=$(echo "$response" | head -1) + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n1) check_response "$http_status" "$response_body" "SonarQube data processing failed." status=$(echo "$response_body" | jq -r '.current.status') if [ "$status" == "SUCCESS" ]; then + # Stop spinner if data processing is complete + stop_spinner "$spinner_pid" echo -e "\nโœ… Data has been fully uploaded and processed by SonarQube!" break elif [ "$waited" -ge "$TIMEOUT_PERIOD" ]; then + stop_spinner "$spinner_pid" echo -e "\nโŒ SonarQube did not finish processing the data within $TIMEOUT_PERIOD seconds." exit 1 fi - echo -n "." sleep "$interval" waited=$((waited + interval)) done @@ -52,6 +68,9 @@ run_sonarscanner() { run_codecharta_analysis() { echo "๐Ÿ“Š Running CodeCharta analysis..." + # Print start of dimmed output + echo -e "\033[2m" # Start dimming the text + # Use the correct hostname 'sonarqube' and execute the analysis docker run --rm -it --network "$NETWORK_NAME" --name codecharta-analysis \ -v "$PROJECT_BASEDIR:$PROJECT_BASEDIR" \ @@ -59,6 +78,9 @@ run_codecharta_analysis() { codecharta/codecharta-analysis \ ccsh sonarimport "$CONTAINER_SONAR_URL" "$PROJECT_KEY" "--user-token=$token" "--output-file=$PROJECT_BASEDIR/sonar.cc.json" "--merge-modules=false" + # Stop dimming after the Docker command completes + echo -e "\033[0m" # Reset to normal text + if [ $? -ne 0 ]; then echo "โŒ CodeCharta analysis failed." exit 1 diff --git a/script/automated_sonar_analysis/helpers.sh b/script/automated_sonar_analysis/helpers.sh index 81ee75e39f..10fbeb096d 100644 --- a/script/automated_sonar_analysis/helpers.sh +++ b/script/automated_sonar_analysis/helpers.sh @@ -117,8 +117,7 @@ generate_token() { fi echo "โœ… Token generated: $token" - # echo "Token response:" - # echo "$response" | jq '.' + } urlencode() { @@ -126,4 +125,24 @@ urlencode() { local encoded_str encoded_str=$(jq -rn --arg v "$raw_str" '$v|@uri') echo "$encoded_str" -} \ No newline at end of file +} + +start_spinner() { + local message=$1 # The message to display next to the spinner + local spinner="โ ‹โ ™โ นโ ธโ ผโ ดโ ฆโ งโ ‡โ " # Spinner animation frames + local spinner_length=${#spinner} + local i=0 # Spinner index + + while :; do + i=$(( (i+1) % spinner_length )) # Loop through spinner characters + echo -ne "\r\033[1;33m$message ${spinner:i:1} \033[0m" # Yellow text with spinner + sleep 0.1 # Small delay between character updates (controls speed) + done +} + +stop_spinner() { + kill "$1" # Kill the spinner process + wait "$1" 2>/dev/null # Wait for the spinner to stop and suppress errors + echo -ne "\r\033[0m" # Reset any terminal color/formatting and clear line +} + diff --git a/script/automated_sonar_analysis/sonarqube_management.sh b/script/automated_sonar_analysis/sonarqube_management.sh index 5086820566..0aaf6d2dc0 100644 --- a/script/automated_sonar_analysis/sonarqube_management.sh +++ b/script/automated_sonar_analysis/sonarqube_management.sh @@ -44,37 +44,40 @@ ensure_sonarqube_running() { } wait_for_sonarqube_ready() { - echo "โณ Waiting for SonarQube to be ready..." + # Start spinner in the background with a custom message + start_spinner "โณ Checking SonarQube status..." & + spinner_pid=$! local check_interval=2 # Time to wait between each check (in seconds) - local elapsed_time=0 # Track the total elapsed time + local elapsed_time=0 # Track the total elapsed time local response local sonarqube_status while [ $elapsed_time -lt $TIMEOUT_PERIOD ]; do response=$(curl -s -w "\n%{http_code}" "$HOST_SONAR_URL/api/system/status") - http_status=$(echo "$response" | tail -1) - response_body=$(echo "$response" | head -1) - - check_response "$http_status" "$response_body" "Checking SonarQube readiness failed." + http_status=$(echo "$response" | tail -n1) + response_body=$(echo "$response" | head -n1) sonarqube_status=$(echo "$response_body" | jq -r '.status') if [ "$sonarqube_status" == "UP" ]; then - echo -e "\nโœ… SonarQube is ready!" + stop_spinner "$spinner_pid" # Stop spinner if SonarQube is ready + echo -e "\nโœ… SonarQube is ready!" # Green success message return - else - echo -n "." - sleep $check_interval - elapsed_time=$((elapsed_time + check_interval)) fi + + sleep $check_interval + elapsed_time=$((elapsed_time + check_interval)) done - echo "โŒ SonarQube did not become ready within $timeout_period seconds." + # If the loop ends without success, stop the spinner and show an error message + stop_spinner "$spinner_pid" + echo -e "\nโŒ SonarQube did not become ready within $TIMEOUT_PERIOD seconds." # Red error message exit 1 } + reset_sonarqube_password() { echo "๐Ÿ” Testing SonarQube credentials: Username='$DEFAULT_SONAR_USER', Password='$DEFAULT_SONAR_PASSWORD'" From 839024664c82cc879113b8bfe33137741d2cf094 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Thu, 5 Sep 2024 10:52:14 +0200 Subject: [PATCH 30/40] fix: Check if docker Daemon is running --- .../automated_sonar_analysis/dependency_checker.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/script/automated_sonar_analysis/dependency_checker.sh b/script/automated_sonar_analysis/dependency_checker.sh index 9b36a794b7..41428b4420 100644 --- a/script/automated_sonar_analysis/dependency_checker.sh +++ b/script/automated_sonar_analysis/dependency_checker.sh @@ -82,6 +82,19 @@ check_docker() { fi } +# Function to check if Docker Daemon is running +check_docker_daemon() { + if docker info >/dev/null 2>&1; then + echo "โœ… Docker Daemon is running." + else + echo "โŒ Docker Daemon is not running." + echo "๐Ÿ’ป Please start Docker Daemon manually." + echo "๐Ÿ”— Visit https://docs.docker.com/config/daemon/ for more information." + exit 1 + fi +} + + # Function to ensure Docker images are pulled docker_pull_image() { local image=$1 @@ -137,6 +150,7 @@ check_docker_images() { # Run the checks for jq and Docker check_jq check_docker +check_docker_daemon # Check for required Docker images check_docker_images From 9b0b52484a839c6cb330d38e5761fb38d95454c7 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Thu, 5 Sep 2024 13:20:09 +0200 Subject: [PATCH 31/40] chore: add docs --- script/automated_sonar_analysis/README.md | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 script/automated_sonar_analysis/README.md diff --git a/script/automated_sonar_analysis/README.md b/script/automated_sonar_analysis/README.md new file mode 100644 index 0000000000..8057d7dcd7 --- /dev/null +++ b/script/automated_sonar_analysis/README.md @@ -0,0 +1,63 @@ +# SonarQube & CodeCharta Automation Script + +## Overview + +This script automates the setup and analysis processes for SonarQube and CodeCharta on Linux and MacOS. It handles: + +1. **SonarQube Project Setup**: Creates a SonarQube project and optionally resets the default 'admin' password. +2. **Source Code Analysis**: Runs SonarScanner to analyze the project's source code. +3. **CodeCharta Analysis**: Performs a CodeCharta analysis based on the scanned data. + +You can choose to use default values or provide custom configurations when running the script. To skip prompts and use default values, use the `-s` flag. + +## Configuration Variables + +- **PROJECT_KEY**: A unique identifier for the project in SonarQube. Default: `maibornwolff-gmbh_codecharta_visualization` +- **PROJECT_NAME**: The name of the project in SonarQube. Default: `CodeCharta Visualization` +- **NEW_SONAR_PASSWORD**: The new password for the SonarQube admin user. Default: `newadminpassword` +- **PROJECT_BASEDIR**: The directory containing the source code to be analyzed. Default: Path to the `visualization` directory relative to the script location. +- **HOST_SONAR_URL**: URL used by the host machine to access the SonarQube server. Default: `http://localhost:9000` +- **CONTAINER_SONAR_URL**: URL used by Docker containers to access the SonarQube server. Default: `http://sonarqube:9000` +- **DEFAULT_SONAR_USER**: Default SonarQube admin user. Default: `admin` +- **DEFAULT_SONAR_PASSWORD**: Default SonarQube admin password. Default: `admin` +- **SONARQUBE_TOKEN_NAME**: Name for the SonarQube token. Default: `codecharta_token` +- **SONARQUBE_TOKEN**: SonarQube token (initialized as empty). +- **NETWORK_NAME**: Docker network name. Default: `sonarnet` +- **SONAR_CONTAINER_NAME**: Docker container name for SonarQube. Default: `sonarqube` +- **RUN_PROJECT_CLEANUP**: Set to `true` to delete the existing SonarQube project. Default: `true` +- **RUN_SONAR_SCANNER**: Set to `true` to run SonarScanner. Default: `true` +- **RUN_FINAL_CLEANUP**: Set to `true` to run the final cleanup of Docker containers and networks. Default: `false` +- **TIMEOUT_PERIOD**: Timeout period in seconds for waiting on SonarQube data processing and startup. Default: `10000` + +## Script Execution + +1. **Introduction**: Displays the purpose of the script and usage instructions. +2. **Prompt for Configuration**: If the `-s` flag is not used, prompts for the following: + - Project Key + - Project Name + - SonarQube Admin Password + - Directory Path for Scanning +3. **Encode Project Key and Name**: URL-encodes the project key and name for safe usage. +4. **Run Steps**: + - Ensure SonarQube is running. + - Reset SonarQube admin password (if required). + - Clean up the previous SonarQube project (if configured). + - Revoke existing token. + - Create a new SonarQube project and generate a token. + - Run SonarScanner for code analysis (if configured). + - Perform CodeCharta analysis. + - Run final cleanup (if configured). + +## Usage + +### Default Execution + +```bash +./run_analysis.sh +``` + +### Skip Prompts + +```bash +./run_analysis.sh -s +``` From 18bbf0cabfc52fc067384c1fe21c0b265689e806 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Thu, 5 Sep 2024 13:47:23 +0200 Subject: [PATCH 32/40] docs: add how to docs to github pages --- .../2024-09-05-automated-sonar-analysis.md | 74 +++++++++++++++++++ script/automated_sonar_analysis/README.md | 4 +- 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md diff --git a/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md new file mode 100644 index 0000000000..29855f9325 --- /dev/null +++ b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md @@ -0,0 +1,74 @@ +--- +categories: + - How-to +tags: + - sonarimport + - analysis +title: Automated SonarQube Analysis +--- + +# SonarQube & CodeCharta Automation Script + +## Overview + +This script automates the setup and analysis processes for SonarQube and CodeCharta on Linux and MacOS. It handles: + +1. **SonarQube Project Setup**: Creates a SonarQube project and optionally resets the default 'admin' password. +2. **Source Code Analysis**: Runs SonarScanner to analyze the project's source code. +3. **CodeCharta Analysis**: Performs a CodeCharta analysis based on the scanned data. + +You can choose to use default values or provide custom configurations when running the script. To skip prompts and use default values, use the `-s` flag. + +## Configuration Variables + +- **PROJECT_KEY**: A unique identifier for the project in SonarQube. Default: `maibornwolff-gmbh_codecharta_visualization` +- **PROJECT_NAME**: The name of the project in SonarQube. Default: `CodeCharta Visualization` +- **NEW_SONAR_PASSWORD**: The new password for the SonarQube admin user. Default: `newadminpassword` +- **PROJECT_BASEDIR**: The directory containing the source code to be analyzed. Default: Path to the `visualization` directory relative to the script location. +- **HOST_SONAR_URL**: URL used by the host machine to access the SonarQube server. Default: `http://localhost:9000` +- **CONTAINER_SONAR_URL**: URL used by Docker containers to access the SonarQube server. Default: `http://sonarqube:9000` +- **DEFAULT_SONAR_USER**: Default SonarQube admin user. Default: `admin` +- **DEFAULT_SONAR_PASSWORD**: Default SonarQube admin password. Default: `admin` +- **SONARQUBE_TOKEN_NAME**: Name for the SonarQube token. Default: `codecharta_token` +- **SONARQUBE_TOKEN**: SonarQube token (initialized as empty). +- **NETWORK_NAME**: Docker network name. Default: `sonarnet` +- **SONAR_CONTAINER_NAME**: Docker container name for SonarQube. Default: `sonarqube` +- **RUN_PROJECT_CLEANUP**: Set to `true` to delete the existing SonarQube project. Default: `true` +- **RUN_SONAR_SCANNER**: Set to `true` to run SonarScanner. Default: `true` +- **RUN_FINAL_CLEANUP**: Set to `true` to run the final cleanup of Docker containers and networks. Default: `false` +- **TIMEOUT_PERIOD**: Timeout period in seconds for waiting on SonarQube data processing and startup. Default: `10000` + +## Script Execution + +1. **Introduction**: Displays the purpose of the script and usage instructions. +2. **Prompt for Configuration**: If the `-s` flag is not used, prompts for the following: + - Project Key + - Project Name + - SonarQube Admin Password + - Directory Path for Scanning +3. **Encode Project Key and Name**: URL-encodes the project key and name for safe usage. +4. **Run Steps**: + - Ensure SonarQube is running. + - Reset SonarQube admin password (if required). + - Clean up the previous SonarQube project (if configured). + - Revoke existing token. + - Create a new SonarQube project and generate a token. + - Run SonarScanner for code analysis (if configured). + - Perform CodeCharta analysis. + - Run final cleanup (if configured). + +## Usage + +### Default Execution + +```bash +git clone https://github.com/MaibornWolff/codecharta.git +./script/automated_sonar_analysis/run_analysis.sh +``` + +### Skip Prompts + +```bash +git clone https://github.com/MaibornWolff/codecharta.git +./script/automated_sonar_analysis/run_analysis.sh -s +``` diff --git a/script/automated_sonar_analysis/README.md b/script/automated_sonar_analysis/README.md index 8057d7dcd7..37ea2c40e3 100644 --- a/script/automated_sonar_analysis/README.md +++ b/script/automated_sonar_analysis/README.md @@ -53,11 +53,11 @@ You can choose to use default values or provide custom configurations when runni ### Default Execution ```bash -./run_analysis.sh +./script/automated_sonar_analysis/run_analysis.sh ``` ### Skip Prompts ```bash -./run_analysis.sh -s +./script/automated_sonar_analysis/run_analysis.sh -s ``` From 99747c6cee886c398ff0b3a62994d3911f655980 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Sun, 8 Sep 2024 20:55:53 +0200 Subject: [PATCH 33/40] feat: Add menu functionality to select steps and improve user experience --- script/automated_sonar_analysis/helpers.sh | 109 ++++++++++++++++++ .../automated_sonar_analysis/run_analysis.sh | 69 ++++++----- 2 files changed, 151 insertions(+), 27 deletions(-) diff --git a/script/automated_sonar_analysis/helpers.sh b/script/automated_sonar_analysis/helpers.sh index 10fbeb096d..bd4308eb63 100644 --- a/script/automated_sonar_analysis/helpers.sh +++ b/script/automated_sonar_analysis/helpers.sh @@ -146,3 +146,112 @@ stop_spinner() { echo -ne "\r\033[0m" # Reset any terminal color/formatting and clear line } +show_menu() { + declare -i num_args; + declare -a options preselected; + + # Unpack options array + num_args=$1; shift + while (( num_args-- > 0 )); do + options+=( "$1" ) + shift + done + + # Unpack preselected array + num_args=$1; shift + while (( num_args-- > 0 )); do + preselected+=( "$1" ) + shift + done + + local selected=() + local cursor=0 + local n_options=${#options[@]} + + # Set preselected options + for i in "${preselected[@]}"; do + selected[$i]="1" + done + + # Switch to alternate screen buffer + tput smcup + tput clear # Clear the alternate screen + + # Save the current cursor position + tput sc + + # Display the menu and interact with the user + while true; do + # Move the cursor back to the initial position + tput rc # Restore the cursor position to where the menu starts + tput ed # Clear everything below the cursor + + echo -e "\n\e[1;36mโœจ Use\e[1;33m โฌ†๏ธ \ โฌ‡๏ธ \e[1;36m to navigate, \e[1;33m[โฃ SPACE]\e[1;36m to select/deselect, and \e[1;33m[โŽ ENTER]\e[1;36m to confirm your choices. โœจ\e[0m\n" + # Redraw the menu + for i in "${!options[@]}"; do + if [ "$i" -eq "$cursor" ]; then + if [[ "${selected[i]}" == "1" ]]; then + # Hovered & selected option + echo -e "-> \e[1;32m[โœ”] ${options[i]}\e[0m" # Bold green for both hovered and selected + else + # Hovered option (not selected) + echo -e "-> \e[1;33m[ ] ${options[i]}\e[0m" # Bold yellow for hovered + fi + else + if [[ "${selected[i]}" == "1" ]]; then + # Selected option (not hovered) + echo -e " \e[32m[โœ”] ${options[i]}\e[0m" # Regular green for selected + else + # Neither hovered nor selected + echo -e " [ ] ${options[i]}" # Regular for non-selected, non-hovered + fi + fi + done + + # Read key input + IFS= read -rsn1 key + + if [[ $key == "" ]]; then + # Enter key pressed, break out of loop + break + elif [[ $key == " " ]]; then + # Space bar pressed, toggle selection + if [[ "${selected[cursor]}" == "1" ]]; then + selected[cursor]="" + else + selected[cursor]="1" + fi + elif [[ $key == $'\x1b' ]]; then + # Handle arrow keys + read -rsn2 key + if [[ $key == "[A" ]]; then + # Up arrow + ((cursor--)) + if [ $cursor -lt 0 ]; then + cursor=$((n_options - 1)) + fi + elif [[ $key == "[B" ]]; then + # Down arrow + ((cursor++)) + if [ $cursor -ge $n_options ]; then + cursor=0 + fi + fi + fi + done + + # Store the selected options in a global array + selected_options=() # Clear any previous selections + for i in "${!options[@]}"; do + if [[ "${selected[i]}" == "1" ]]; then + selected_options+=("${options[i]}") + fi + done + + # Restore the terminal to its original state + tput rmcup +} + +# options=("Option 1" "Option 2" "Option 3" "Option 4") +# preselected=(0 2) # Preselect Option 1 and Option 3 +# show_menu "${#options[@]}" "${options[@]}" "${#preselected[@]}" "${preselected[@]}" diff --git a/script/automated_sonar_analysis/run_analysis.sh b/script/automated_sonar_analysis/run_analysis.sh index 7f6b036e73..c85250a847 100644 --- a/script/automated_sonar_analysis/run_analysis.sh +++ b/script/automated_sonar_analysis/run_analysis.sh @@ -43,7 +43,7 @@ RUN_SONAR_SCANNER=true # Set to true to run the final cleanup of Docker containers and networks RUN_FINAL_CLEANUP=false # Timeout period in seconds for waiting on SonarQube data processing and startup -TIMEOUT_PERIOD=10000 +TIMEOUT_PERIOD=10000 ### Main Script ############################################################### @@ -95,33 +95,48 @@ fi ENCODED_PROJECT_KEY=$(urlencode "$PROJECT_KEY") ENCODED_PROJECT_NAME=$(urlencode "$PROJECT_NAME") -# Run the steps -ensure_sonarqube_running +# Present a menu to the user to select which steps to run +steps=("Ensure SonarQube Running" "Reset SonarQube Password" "Clean Up Previous Project" "Revoke Token" "Create Project and Generate Token" "Run SonarScanner" "Run CodeCharta Analysis" "Final Cleanup") -# Conditionally reset password -reset_sonarqube_password +preselected_indices=(0 1 2 3 4 5 6 7) # By default, all steps are preselected -# Conditionally clean up the previous project -if $RUN_PROJECT_CLEANUP; then - cleanup_previous_project -fi - -# Always revoke the existing token -revoke_token - -# Create the project and generate the token -create_sonarqube_project -generate_token - -# Conditionally run the SonarScanner -if $RUN_SONAR_SCANNER; then - run_sonarscanner -fi +# Call the `show_menu` function from helpers.sh +show_menu "${#steps[@]}" "${steps[@]}" "${#preselected_indices[@]}" "${preselected_indices[@]}" -# Run the CodeCharta analysis -run_codecharta_analysis +# Now selected_options array contains the selected items +selected_steps=("${selected_options[@]}") +echo -e "\nRunning:" +for i in "${!selected_options[@]}"; do + echo " $((i + 1))) ${selected_options[i]}" +done -# Final cleanup if enabled -if $RUN_FINAL_CLEANUP; then - cleanup -fi +# Execute the steps based on user selection +for step in "${selected_steps[@]}"; do + case $step in + "Ensure SonarQube Running") + ensure_sonarqube_running + ;; + "Reset SonarQube Password") + reset_sonarqube_password + ;; + "Clean Up Previous Project") + cleanup_previous_project + ;; + "Revoke Token") + revoke_token + ;; + "Create Project and Generate Token") + create_sonarqube_project + generate_token + ;; + "Run SonarScanner") + run_sonarscanner + ;; + "Run CodeCharta Analysis") + run_codecharta_analysis + ;; + "Final Cleanup") + cleanup + ;; + esac +done From 08e114c20bdddd0ddb3ae13c59b4994596f74a81 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Mon, 9 Sep 2024 17:29:42 +0200 Subject: [PATCH 34/40] refactor: Refactor dependency checking and add documentation - Refactor the dependency checking code in the `dependency_checker.sh` script to improve readability and maintainability. - Add documentation to the `README.md` file explaining how to use the script with default values, custom configurations, and reusable commands. --- .../2024-09-05-automated-sonar-analysis.md | 37 ++++- script/automated_sonar_analysis/README.md | 35 ++++- .../dependency_checker.sh | 16 +- .../automated_sonar_analysis/run_analysis.sh | 144 ++++++++++++++---- 4 files changed, 184 insertions(+), 48 deletions(-) diff --git a/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md index 29855f9325..b7e3093944 100644 --- a/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md +++ b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md @@ -17,7 +17,7 @@ This script automates the setup and analysis processes for SonarQube and CodeCha 2. **Source Code Analysis**: Runs SonarScanner to analyze the project's source code. 3. **CodeCharta Analysis**: Performs a CodeCharta analysis based on the scanned data. -You can choose to use default values or provide custom configurations when running the script. To skip prompts and use default values, use the `-s` flag. +You can choose to use default values or provide custom configurations when running the script. To skip prompts and use default values, use the `-s` flag. After execution, the script will print a reusable command with the provided configurations, which you can use next time to skip prompts. ## Configuration Variables @@ -46,8 +46,9 @@ You can choose to use default values or provide custom configurations when runni - Project Name - SonarQube Admin Password - Directory Path for Scanning -3. **Encode Project Key and Name**: URL-encodes the project key and name for safe usage. -4. **Run Steps**: +3. **Build and Display Reusable Command**: After gathering inputs (whether via flags or prompts), the script builds a reusable command reflecting the provided configurations and prints it at the end for future use. +4. **Encode Project Key and Name**: URL-encodes the project key and name for safe usage. +5. **Run Steps**: - Ensure SonarQube is running. - Reset SonarQube admin password (if required). - Clean up the previous SonarQube project (if configured). @@ -62,13 +63,39 @@ You can choose to use default values or provide custom configurations when runni ### Default Execution ```bash -git clone https://github.com/MaibornWolff/codecharta.git ./script/automated_sonar_analysis/run_analysis.sh ``` ### Skip Prompts ```bash -git clone https://github.com/MaibornWolff/codecharta.git ./script/automated_sonar_analysis/run_analysis.sh -s ``` + +### Custom Execution with Flags + +You can provide flags to customize the execution. For example: + +```bash +./script/automated_sonar_analysis/run_analysis.sh -k "custom_project_key" -n "Custom Project Name" -p "new_password" -d "/path/to/codebase" +``` + +### Reusable Command + +After running the script, it will display a command you can use to execute the script with the same parameters without prompting next time. This allows for easy reuse of the configurations you provided during the first run. + +Example reusable command generated: + +```bash +./script/automated_sonar_analysis/run_analysis.sh -k "custom_project_key" -n "Custom Project Name" -p "new_password" -d "/path/to/codebase" -u "http://localhost:9000" -t "codecharta_token" +``` + +This command will automatically use the values you previously provided, making future executions more efficient. + +### Key Points: + +- The documentation is structured using `#`, `##`, and `###` headers to clearly delineate sections. +- Code blocks for execution examples are formatted using triple backticks (` ```bash `) to ensure they are displayed correctly. +- Each command example is displayed cleanly without breaking Markdown rendering. + +This format provides clarity and ease of use for the script users. diff --git a/script/automated_sonar_analysis/README.md b/script/automated_sonar_analysis/README.md index 37ea2c40e3..916fe635e2 100644 --- a/script/automated_sonar_analysis/README.md +++ b/script/automated_sonar_analysis/README.md @@ -8,7 +8,7 @@ This script automates the setup and analysis processes for SonarQube and CodeCha 2. **Source Code Analysis**: Runs SonarScanner to analyze the project's source code. 3. **CodeCharta Analysis**: Performs a CodeCharta analysis based on the scanned data. -You can choose to use default values or provide custom configurations when running the script. To skip prompts and use default values, use the `-s` flag. +You can choose to use default values or provide custom configurations when running the script. To skip prompts and use default values, use the `-s` flag. After execution, the script will print a reusable command with the provided configurations, which you can use next time to skip prompts. ## Configuration Variables @@ -37,8 +37,9 @@ You can choose to use default values or provide custom configurations when runni - Project Name - SonarQube Admin Password - Directory Path for Scanning -3. **Encode Project Key and Name**: URL-encodes the project key and name for safe usage. -4. **Run Steps**: +3. **Build and Display Reusable Command**: After gathering inputs (whether via flags or prompts), the script builds a reusable command reflecting the provided configurations and prints it at the end for future use. +4. **Encode Project Key and Name**: URL-encodes the project key and name for safe usage. +5. **Run Steps**: - Ensure SonarQube is running. - Reset SonarQube admin password (if required). - Clean up the previous SonarQube project (if configured). @@ -61,3 +62,31 @@ You can choose to use default values or provide custom configurations when runni ```bash ./script/automated_sonar_analysis/run_analysis.sh -s ``` + +### Custom Execution with Flags + +You can provide flags to customize the execution. For example: + +```bash +./script/automated_sonar_analysis/run_analysis.sh -k "custom_project_key" -n "Custom Project Name" -p "new_password" -d "/path/to/codebase" +``` + +### Reusable Command + +After running the script, it will display a command you can use to execute the script with the same parameters without prompting next time. This allows for easy reuse of the configurations you provided during the first run. + +Example reusable command generated: + +```bash +./script/automated_sonar_analysis/run_analysis.sh -k "custom_project_key" -n "Custom Project Name" -p "new_password" -d "/path/to/codebase" -u "http://localhost:9000" -t "codecharta_token" +``` + +This command will automatically use the values you previously provided, making future executions more efficient. + +### Key Points: + +- The documentation is structured using `#`, `##`, and `###` headers to clearly delineate sections. +- Code blocks for execution examples are formatted using triple backticks (` ```bash `) to ensure they are displayed correctly. +- Each command example is displayed cleanly without breaking Markdown rendering. + +This format provides clarity and ease of use for the script users. diff --git a/script/automated_sonar_analysis/dependency_checker.sh b/script/automated_sonar_analysis/dependency_checker.sh index 41428b4420..275c7e871e 100644 --- a/script/automated_sonar_analysis/dependency_checker.sh +++ b/script/automated_sonar_analysis/dependency_checker.sh @@ -147,12 +147,14 @@ check_docker_images() { fi } -# Run the checks for jq and Docker -check_jq -check_docker -check_docker_daemon +check_dependencies () { + # Run the checks for jq and Docker + check_jq + check_docker + check_docker_daemon -# Check for required Docker images -check_docker_images + # Check for required Docker images + check_docker_images -echo "๐ŸŽ‰ All dependencies are installed and required Docker images are available." + echo "๐ŸŽ‰ All dependencies are installed and required Docker images are available." +} diff --git a/script/automated_sonar_analysis/run_analysis.sh b/script/automated_sonar_analysis/run_analysis.sh index c85250a847..a61fa58f1d 100644 --- a/script/automated_sonar_analysis/run_analysis.sh +++ b/script/automated_sonar_analysis/run_analysis.sh @@ -11,9 +11,9 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi . "$DIR/sonarqube_management.sh" . "$DIR/analysers.sh" -### Configuration ############################################################# +### Default Configuration ##################################################### -# PROJECT_KEY: A unique identifier for the project in SonarQube. +# Default values for various variables PROJECT_KEY="maibornwolff-gmbh_codecharta_visualization" # PROJECT_NAME: The name of the project in SonarQube. @@ -39,62 +39,131 @@ SONAR_CONTAINER_NAME="sonarqube" # Set to true to delete the existing SonarQube project RUN_PROJECT_CLEANUP=true # Set to true to run SonarScanner -RUN_SONAR_SCANNER=true +RUN_SONAR_SCANNER=true # Set to true to run the final cleanup of Docker containers and networks -RUN_FINAL_CLEANUP=false +RUN_FINAL_CLEANUP=false # Timeout period in seconds for waiting on SonarQube data processing and startup TIMEOUT_PERIOD=10000 +# Flags to check if specific options were passed +FLAG_PROJECT_KEY=false +FLAG_PROJECT_NAME=false +FLAG_NEW_SONAR_PASSWORD=false +FLAG_PROJECT_BASEDIR=false +FLAG_HOST_SONAR_URL=false +FLAG_SONARQUBE_TOKEN_NAME=false + +### Help Message ############################################################## + +show_help() { + echo "Usage: ${0##*/} [-k ] [-n ] [-p ] [-d ] [-u ] [-t ] [-s] [-h]" + echo "" + echo "Options:" + echo " -k Set the project key (default: $PROJECT_KEY)" + echo " -n Set the project name (default: $PROJECT_NAME)" + echo " -p Set the new SonarQube admin password (default: $NEW_SONAR_PASSWORD)" + echo " -d Set the directory to be scanned (default: $PROJECT_BASEDIR)" + echo " -u Set the host SonarQube URL (default: $HOST_SONAR_URL)" + echo " -t Set the SonarQube token name (default: $SONARQUBE_TOKEN_NAME)" + echo " -s Skip all prompts and use default values or passed flags" + echo " -h Show this help message and exit" +} + +### Parse Flags ############################################################### + +while getopts ":k:n:p:d:u:t:sh" opt; do + case ${opt} in + k ) # Project Key + PROJECT_KEY=$OPTARG + FLAG_PROJECT_KEY=true + ;; + n ) # Project Name + PROJECT_NAME=$OPTARG + FLAG_PROJECT_NAME=true + ;; + p ) # SonarQube admin password + NEW_SONAR_PASSWORD=$OPTARG + FLAG_NEW_SONAR_PASSWORD=true + ;; + d ) # Project Base Directory + PROJECT_BASEDIR=$OPTARG + FLAG_PROJECT_BASEDIR=true + ;; + u ) # Host Sonar URL + HOST_SONAR_URL=$OPTARG + FLAG_HOST_SONAR_URL=true + ;; + t ) # SonarQube token name + SONARQUBE_TOKEN_NAME=$OPTARG + FLAG_SONARQUBE_TOKEN_NAME=true + ;; + s ) # Skip all prompts + SKIP_PROMPT=true + ;; + h ) # Show help + show_help + exit 0 + ;; + \? ) # Invalid option + echo "Invalid option: -$OPTARG" 1>&2 + show_help + exit 1 + ;; + : ) # Missing argument + echo "Option -$OPTARG requires an argument." 1>&2 + show_help + exit 1 + ;; + esac +done ### Main Script ############################################################### -# Introductory message explaining the script's purpose echo -e "๐Ÿ”ง Welcome to the SonarQube & CodeCharta Automation Script ๐Ÿ”ง" echo -e "------------------------------------------------------------" echo -e "This script automates the process of:" echo -e "1. Setting up a SonarQube project and resetting the default 'admin' password if needed." echo -e "2. Running SonarScanner to analyze your project's source code." echo -e "3. Conducting a CodeCharta analysis of the scanned data." -echo -e "\nYou can choose to provide custom values for the project configuration or use the defaults." -echo -e "To skip the prompts and use all default values, run the script with the -s flag." -echo -e "If the default 'admin' password is still in use, the script will change it to the new password you provide." -echo -e "Note: This is only relevant for users who do not already have an instance of SonarQube running." echo -e "------------------------------------------------------------\n" -# Check for skip prompt flag -SKIP_PROMPT=false -while getopts ":s" opt; do - case ${opt} in - s ) - SKIP_PROMPT=true - ;; - \? ) - echo "Invalid option: -$OPTARG" 1>&2 - exit 1 - ;; - esac -done -# If skip prompt mode is not enabled, prompt for important variables with defaults -if [ "$SKIP_PROMPT" = false ]; then - # Allow the user to override the default values if desired - read -p "๐Ÿ”‘ Enter the Project Key (default: $PROJECT_KEY): " input - PROJECT_KEY=${input:-$PROJECT_KEY} +# Prompt for important variables only if they weren't provided via flags +if [ "$SKIP_PROMPT" != true ]; then + if [ "$FLAG_PROJECT_KEY" = false ]; then + read -p "๐Ÿ”‘ Enter the Project Key (default: $PROJECT_KEY): " input + PROJECT_KEY=${input:-$PROJECT_KEY} + fi - read -p "๐Ÿ“› Enter the Project Name (default: $PROJECT_NAME): " input - PROJECT_NAME=${input:-$PROJECT_NAME} + if [ "$FLAG_PROJECT_NAME" = false ]; then + read -p "๐Ÿ“› Enter the Project Name (default: $PROJECT_NAME): " input + PROJECT_NAME=${input:-$PROJECT_NAME} + fi - read -p "๐Ÿ”’ Enter the new password for the SonarQube admin user (default: $NEW_SONAR_PASSWORD): " input - NEW_SONAR_PASSWORD=${input:-$NEW_SONAR_PASSWORD} + if [ "$FLAG_NEW_SONAR_PASSWORD" = false ]; then + read -p "๐Ÿ”’ Enter the new password for the SonarQube admin user (default: $NEW_SONAR_PASSWORD): " input + NEW_SONAR_PASSWORD=${input:-$NEW_SONAR_PASSWORD} + fi - read -p "๐Ÿ“ Enter the directory path to be scanned (default: $PROJECT_BASEDIR): " input - PROJECT_BASEDIR=${input:-$PROJECT_BASEDIR} + if [ "$FLAG_PROJECT_BASEDIR" = false ]; then + read -p "๐Ÿ“ Enter the directory path to be scanned (default: $PROJECT_BASEDIR): " input + PROJECT_BASEDIR=${input:-$PROJECT_BASEDIR} + fi fi # URL-encode PROJECT_KEY and PROJECT_NAME ENCODED_PROJECT_KEY=$(urlencode "$PROJECT_KEY") ENCODED_PROJECT_NAME=$(urlencode "$PROJECT_NAME") +# Build reusable command +cmd="./${0##*/}" +cmd+=" -k \"$PROJECT_KEY\"" +cmd+=" -n \"$PROJECT_NAME\"" +cmd+=" -p \"$NEW_SONAR_PASSWORD\"" +cmd+=" -d \"$PROJECT_BASEDIR\"" +cmd+=" -u \"$HOST_SONAR_URL\"" +cmd+=" -t \"$SONARQUBE_TOKEN_NAME\"" + # Present a menu to the user to select which steps to run steps=("Ensure SonarQube Running" "Reset SonarQube Password" "Clean Up Previous Project" "Revoke Token" "Create Project and Generate Token" "Run SonarScanner" "Run CodeCharta Analysis" "Final Cleanup") @@ -110,6 +179,10 @@ for i in "${!selected_options[@]}"; do echo " $((i + 1))) ${selected_options[i]}" done +### Run the steps ############################################################# + +check_dependencies + # Execute the steps based on user selection for step in "${selected_steps[@]}"; do case $step in @@ -140,3 +213,8 @@ for step in "${selected_steps[@]}"; do ;; esac done + +### Print Reusable Command #################################################### + +echo -e "\nTo run this script again without prompts, use the following command:" +echo "$cmd" From 2f1446107473efd28043bb011d89d06159ffdc30 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Tue, 10 Sep 2024 11:07:56 +0200 Subject: [PATCH 35/40] chore: remove output --- visualization/sonar.cc.json.gz | Bin 74900 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 visualization/sonar.cc.json.gz diff --git a/visualization/sonar.cc.json.gz b/visualization/sonar.cc.json.gz deleted file mode 100644 index 87bde899d2c8b7d1119867e4a2b9c3ea2393ed24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74900 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%0Ane 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 From 3bb18db86f74f8197bc178dcae53a5599c65cb1c Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Tue, 10 Sep 2024 11:15:11 +0200 Subject: [PATCH 36/40] refactor: Update menu navigation instructions in helpers.sh --- script/automated_sonar_analysis/helpers.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/automated_sonar_analysis/helpers.sh b/script/automated_sonar_analysis/helpers.sh index bd4308eb63..482f146c8f 100644 --- a/script/automated_sonar_analysis/helpers.sh +++ b/script/automated_sonar_analysis/helpers.sh @@ -186,7 +186,7 @@ show_menu() { tput rc # Restore the cursor position to where the menu starts tput ed # Clear everything below the cursor - echo -e "\n\e[1;36mโœจ Use\e[1;33m โฌ†๏ธ \ โฌ‡๏ธ \e[1;36m to navigate, \e[1;33m[โฃ SPACE]\e[1;36m to select/deselect, and \e[1;33m[โŽ ENTER]\e[1;36m to confirm your choices. โœจ\e[0m\n" + echo -e "\n\e[1;36m Use\e[1;33m โฌ†๏ธ \ โฌ‡๏ธ \e[1;36m to navigate, \e[1;33m[โฃ SPACE]\e[1;36m to select/deselect, and \e[1;33m[โŽ ENTER]\e[1;36m to confirm your choices. \e[0m\n" # Redraw the menu for i in "${!options[@]}"; do if [ "$i" -eq "$cursor" ]; then From 933542acdd6cb6393311f5b9e8345e60161e589a Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Tue, 10 Sep 2024 13:49:05 +0200 Subject: [PATCH 37/40] refactor: Update menu navigation instructions in helpers.sh --- script/automated_sonar_analysis/helpers.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/script/automated_sonar_analysis/helpers.sh b/script/automated_sonar_analysis/helpers.sh index 482f146c8f..3f9add4e59 100644 --- a/script/automated_sonar_analysis/helpers.sh +++ b/script/automated_sonar_analysis/helpers.sh @@ -186,24 +186,24 @@ show_menu() { tput rc # Restore the cursor position to where the menu starts tput ed # Clear everything below the cursor - echo -e "\n\e[1;36m Use\e[1;33m โฌ†๏ธ \ โฌ‡๏ธ \e[1;36m to navigate, \e[1;33m[โฃ SPACE]\e[1;36m to select/deselect, and \e[1;33m[โŽ ENTER]\e[1;36m to confirm your choices. \e[0m\n" + printf "\n\e[1;36m Use\e[1;33m โฌ†๏ธ \ โฌ‡๏ธ \e[1;36m to navigate, \e[1;33m[โฃ SPACE]\e[1;36m to select/deselect, and \e[1;33m[โŽ ENTER]\e[1;36m to confirm your choices. \e[0m\n" # Redraw the menu for i in "${!options[@]}"; do if [ "$i" -eq "$cursor" ]; then if [[ "${selected[i]}" == "1" ]]; then # Hovered & selected option - echo -e "-> \e[1;32m[โœ”] ${options[i]}\e[0m" # Bold green for both hovered and selected + printf -- "-> \e[1;32m[โœ”] ${options[i]}\e[0m\n" # Bold green for both hovered and selected else # Hovered option (not selected) - echo -e "-> \e[1;33m[ ] ${options[i]}\e[0m" # Bold yellow for hovered + printf -- "-> \e[1;33m[ ] ${options[i]}\e[0m\n" # Bold yellow for hovered fi else if [[ "${selected[i]}" == "1" ]]; then # Selected option (not hovered) - echo -e " \e[32m[โœ”] ${options[i]}\e[0m" # Regular green for selected + printf -- " \e[32m[โœ”] ${options[i]}\e[0m\n" # Regular green for selected else # Neither hovered nor selected - echo -e " [ ] ${options[i]}" # Regular for non-selected, non-hovered + printf -- " [ ] ${options[i]}\n" # Regular for non-selected, non-hovered fi fi done From a3cdbc87a7e2efccbb11c504d008fcb1a1748e45 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Wed, 11 Sep 2024 12:01:45 +0200 Subject: [PATCH 38/40] chore: update docs --- .../2024-09-05-automated-sonar-analysis.md | 51 ++++++++----------- script/automated_sonar_analysis/README.md | 51 ++++++++----------- 2 files changed, 40 insertions(+), 62 deletions(-) diff --git a/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md index b7e3093944..3e54fb0b27 100644 --- a/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md +++ b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md @@ -19,25 +19,6 @@ This script automates the setup and analysis processes for SonarQube and CodeCha You can choose to use default values or provide custom configurations when running the script. To skip prompts and use default values, use the `-s` flag. After execution, the script will print a reusable command with the provided configurations, which you can use next time to skip prompts. -## Configuration Variables - -- **PROJECT_KEY**: A unique identifier for the project in SonarQube. Default: `maibornwolff-gmbh_codecharta_visualization` -- **PROJECT_NAME**: The name of the project in SonarQube. Default: `CodeCharta Visualization` -- **NEW_SONAR_PASSWORD**: The new password for the SonarQube admin user. Default: `newadminpassword` -- **PROJECT_BASEDIR**: The directory containing the source code to be analyzed. Default: Path to the `visualization` directory relative to the script location. -- **HOST_SONAR_URL**: URL used by the host machine to access the SonarQube server. Default: `http://localhost:9000` -- **CONTAINER_SONAR_URL**: URL used by Docker containers to access the SonarQube server. Default: `http://sonarqube:9000` -- **DEFAULT_SONAR_USER**: Default SonarQube admin user. Default: `admin` -- **DEFAULT_SONAR_PASSWORD**: Default SonarQube admin password. Default: `admin` -- **SONARQUBE_TOKEN_NAME**: Name for the SonarQube token. Default: `codecharta_token` -- **SONARQUBE_TOKEN**: SonarQube token (initialized as empty). -- **NETWORK_NAME**: Docker network name. Default: `sonarnet` -- **SONAR_CONTAINER_NAME**: Docker container name for SonarQube. Default: `sonarqube` -- **RUN_PROJECT_CLEANUP**: Set to `true` to delete the existing SonarQube project. Default: `true` -- **RUN_SONAR_SCANNER**: Set to `true` to run SonarScanner. Default: `true` -- **RUN_FINAL_CLEANUP**: Set to `true` to run the final cleanup of Docker containers and networks. Default: `false` -- **TIMEOUT_PERIOD**: Timeout period in seconds for waiting on SonarQube data processing and startup. Default: `10000` - ## Script Execution 1. **Introduction**: Displays the purpose of the script and usage instructions. @@ -50,18 +31,34 @@ You can choose to use default values or provide custom configurations when runni 4. **Encode Project Key and Name**: URL-encodes the project key and name for safe usage. 5. **Run Steps**: - Ensure SonarQube is running. - - Reset SonarQube admin password (if required). - - Clean up the previous SonarQube project (if configured). + - Reset SonarQube admin password. + - Clean up the previous SonarQube project. - Revoke existing token. - Create a new SonarQube project and generate a token. - - Run SonarScanner for code analysis (if configured). + - Run SonarScanner for code analysis. - Perform CodeCharta analysis. - - Run final cleanup (if configured). + - Run final cleanup. ## Usage +### Flags + +| Flag | Option | Default Value | Description | +| ---- | -------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------- | +| `-k` | `` | `maibornwolff-gmbh_codecharta_visualization` | Set the project key for SonarQube. | +| `-n` | `` | `CodeCharta Visualization` | Set the project name for SonarQube. | +| `-p` | `` | `newadminpassword` | Set the new SonarQube admin password. | +| `-d` | `` | `../codecharta/visualization` | Set the directory containing the project to be scanned. | +| `-u` | `` | `http://localhost:9000` | Set the URL for the SonarQube host. | +| `-t` | `` | `codecharta_token` | Set the token name for SonarQube authentication. | +| `-s` | - | - | Skip all prompts and use either default values or the flags passed in the command. | +| `-h` | - | - | Show the help message for the script and exit. | + ### Default Execution +These commands assume you are in the root of the project. +For MacOS users, you should have brew installed and bash updated. + ```bash ./script/automated_sonar_analysis/run_analysis.sh ``` @@ -91,11 +88,3 @@ Example reusable command generated: ``` This command will automatically use the values you previously provided, making future executions more efficient. - -### Key Points: - -- The documentation is structured using `#`, `##`, and `###` headers to clearly delineate sections. -- Code blocks for execution examples are formatted using triple backticks (` ```bash `) to ensure they are displayed correctly. -- Each command example is displayed cleanly without breaking Markdown rendering. - -This format provides clarity and ease of use for the script users. diff --git a/script/automated_sonar_analysis/README.md b/script/automated_sonar_analysis/README.md index 916fe635e2..72a2deb05a 100644 --- a/script/automated_sonar_analysis/README.md +++ b/script/automated_sonar_analysis/README.md @@ -10,25 +10,6 @@ This script automates the setup and analysis processes for SonarQube and CodeCha You can choose to use default values or provide custom configurations when running the script. To skip prompts and use default values, use the `-s` flag. After execution, the script will print a reusable command with the provided configurations, which you can use next time to skip prompts. -## Configuration Variables - -- **PROJECT_KEY**: A unique identifier for the project in SonarQube. Default: `maibornwolff-gmbh_codecharta_visualization` -- **PROJECT_NAME**: The name of the project in SonarQube. Default: `CodeCharta Visualization` -- **NEW_SONAR_PASSWORD**: The new password for the SonarQube admin user. Default: `newadminpassword` -- **PROJECT_BASEDIR**: The directory containing the source code to be analyzed. Default: Path to the `visualization` directory relative to the script location. -- **HOST_SONAR_URL**: URL used by the host machine to access the SonarQube server. Default: `http://localhost:9000` -- **CONTAINER_SONAR_URL**: URL used by Docker containers to access the SonarQube server. Default: `http://sonarqube:9000` -- **DEFAULT_SONAR_USER**: Default SonarQube admin user. Default: `admin` -- **DEFAULT_SONAR_PASSWORD**: Default SonarQube admin password. Default: `admin` -- **SONARQUBE_TOKEN_NAME**: Name for the SonarQube token. Default: `codecharta_token` -- **SONARQUBE_TOKEN**: SonarQube token (initialized as empty). -- **NETWORK_NAME**: Docker network name. Default: `sonarnet` -- **SONAR_CONTAINER_NAME**: Docker container name for SonarQube. Default: `sonarqube` -- **RUN_PROJECT_CLEANUP**: Set to `true` to delete the existing SonarQube project. Default: `true` -- **RUN_SONAR_SCANNER**: Set to `true` to run SonarScanner. Default: `true` -- **RUN_FINAL_CLEANUP**: Set to `true` to run the final cleanup of Docker containers and networks. Default: `false` -- **TIMEOUT_PERIOD**: Timeout period in seconds for waiting on SonarQube data processing and startup. Default: `10000` - ## Script Execution 1. **Introduction**: Displays the purpose of the script and usage instructions. @@ -41,18 +22,34 @@ You can choose to use default values or provide custom configurations when runni 4. **Encode Project Key and Name**: URL-encodes the project key and name for safe usage. 5. **Run Steps**: - Ensure SonarQube is running. - - Reset SonarQube admin password (if required). - - Clean up the previous SonarQube project (if configured). + - Reset SonarQube admin password. + - Clean up the previous SonarQube project. - Revoke existing token. - Create a new SonarQube project and generate a token. - - Run SonarScanner for code analysis (if configured). + - Run SonarScanner for code analysis. - Perform CodeCharta analysis. - - Run final cleanup (if configured). + - Run final cleanup. ## Usage +### Flags + +| Flag | Option | Default Value | Description | +| ---- | -------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------- | +| `-k` | `` | `maibornwolff-gmbh_codecharta_visualization` | Set the project key for SonarQube. | +| `-n` | `` | `CodeCharta Visualization` | Set the project name for SonarQube. | +| `-p` | `` | `newadminpassword` | Set the new SonarQube admin password. | +| `-d` | `` | `../codecharta/visualization` | Set the directory containing the project to be scanned. | +| `-u` | `` | `http://localhost:9000` | Set the URL for the SonarQube host. | +| `-t` | `` | `codecharta_token` | Set the token name for SonarQube authentication. | +| `-s` | - | - | Skip all prompts and use either default values or the flags passed in the command. | +| `-h` | - | - | Show the help message for the script and exit. | + ### Default Execution +These commands assume you are in the root of the project. +For MacOS users, you should have brew installed and bash updated. + ```bash ./script/automated_sonar_analysis/run_analysis.sh ``` @@ -82,11 +79,3 @@ Example reusable command generated: ``` This command will automatically use the values you previously provided, making future executions more efficient. - -### Key Points: - -- The documentation is structured using `#`, `##`, and `###` headers to clearly delineate sections. -- Code blocks for execution examples are formatted using triple backticks (` ```bash `) to ensure they are displayed correctly. -- Each command example is displayed cleanly without breaking Markdown rendering. - -This format provides clarity and ease of use for the script users. From 3ee744562509f1c24f3dd34f8b520e1421cca581 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Thu, 12 Sep 2024 13:56:37 +0200 Subject: [PATCH 39/40] chore: update docs --- .../2024-09-05-automated-sonar-analysis.md | 43 +++++++++++++------ script/automated_sonar_analysis/README.md | 43 +++++++++++++------ 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md index 3e54fb0b27..99fdf3a510 100644 --- a/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md +++ b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md @@ -41,37 +41,54 @@ You can choose to use default values or provide custom configurations when runni ## Usage -### Flags - -| Flag | Option | Default Value | Description | -| ---- | -------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------- | -| `-k` | `` | `maibornwolff-gmbh_codecharta_visualization` | Set the project key for SonarQube. | -| `-n` | `` | `CodeCharta Visualization` | Set the project name for SonarQube. | -| `-p` | `` | `newadminpassword` | Set the new SonarQube admin password. | -| `-d` | `` | `../codecharta/visualization` | Set the directory containing the project to be scanned. | -| `-u` | `` | `http://localhost:9000` | Set the URL for the SonarQube host. | -| `-t` | `` | `codecharta_token` | Set the token name for SonarQube authentication. | -| `-s` | - | - | Skip all prompts and use either default values or the flags passed in the command. | -| `-h` | - | - | Show the help message for the script and exit. | +### Parameters + +| Parameter | Description | +| ----------------------- | ---------------------------------------------------------------------------------- | +| `-k ` | Set the project key for SonarQube. | +| `-n ` | Set the project name for SonarQube. | +| `-p ` | Set the new SonarQube admin password. | +| `-d ` | Set the directory containing the project to be scanned. | +| `-u ` | Set the URL for the SonarQube host. | +| `-t ` | Set the token name for SonarQube authentication. | +| `-s` | Skip all prompts and use either default values or the flags passed in the command. | +| `-h` | Show the help message for the script and exit. | + +> **USAGE:** +> +> ```shell +> run_analysis.sh [-h] [-s] [-k ] [-n ] [-p ] [-d ] [-u ] [-t ] +> ``` ### Default Execution These commands assume you are in the root of the project. For MacOS users, you should have brew installed and bash updated. +```shell +# For MacOS you need to give execution permission to the script +chmod +x ./script/automated_sonar_analysis/run_analysis.sh +``` + +No need to pass anything the script will prompt as needed. + ```bash ./script/automated_sonar_analysis/run_analysis.sh ``` ### Skip Prompts +The script will use the defaults and will not prompt at all. + ```bash ./script/automated_sonar_analysis/run_analysis.sh -s ``` ### Custom Execution with Flags -You can provide flags to customize the execution. For example: +You can provide flags to customize the execution. In this case, it will skip the prompt and use the provided parameter. + +For example: ```bash ./script/automated_sonar_analysis/run_analysis.sh -k "custom_project_key" -n "Custom Project Name" -p "new_password" -d "/path/to/codebase" diff --git a/script/automated_sonar_analysis/README.md b/script/automated_sonar_analysis/README.md index 72a2deb05a..852a1b6a6b 100644 --- a/script/automated_sonar_analysis/README.md +++ b/script/automated_sonar_analysis/README.md @@ -32,37 +32,54 @@ You can choose to use default values or provide custom configurations when runni ## Usage -### Flags - -| Flag | Option | Default Value | Description | -| ---- | -------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------- | -| `-k` | `` | `maibornwolff-gmbh_codecharta_visualization` | Set the project key for SonarQube. | -| `-n` | `` | `CodeCharta Visualization` | Set the project name for SonarQube. | -| `-p` | `` | `newadminpassword` | Set the new SonarQube admin password. | -| `-d` | `` | `../codecharta/visualization` | Set the directory containing the project to be scanned. | -| `-u` | `` | `http://localhost:9000` | Set the URL for the SonarQube host. | -| `-t` | `` | `codecharta_token` | Set the token name for SonarQube authentication. | -| `-s` | - | - | Skip all prompts and use either default values or the flags passed in the command. | -| `-h` | - | - | Show the help message for the script and exit. | +### Parameters + +| Parameter | Description | +| ----------------------- | ---------------------------------------------------------------------------------- | +| `-k ` | Set the project key for SonarQube. | +| `-n ` | Set the project name for SonarQube. | +| `-p ` | Set the new SonarQube admin password. | +| `-d ` | Set the directory containing the project to be scanned. | +| `-u ` | Set the URL for the SonarQube host. | +| `-t ` | Set the token name for SonarQube authentication. | +| `-s` | Skip all prompts and use either default values or the flags passed in the command. | +| `-h` | Show the help message for the script and exit. | + +> **USAGE:** +> +> ```shell +> run_analysis.sh [-h] [-s] [-k ] [-n ] [-p ] [-d ] [-u ] [-t ] +> ``` ### Default Execution These commands assume you are in the root of the project. For MacOS users, you should have brew installed and bash updated. +```shell +# For MacOS you need to give execution permission to the script +chmod +x ./script/automated_sonar_analysis/run_analysis.sh +``` + +No need to pass anything the script will prompt as needed. + ```bash ./script/automated_sonar_analysis/run_analysis.sh ``` ### Skip Prompts +The script will use the defaults and will not prompt at all. + ```bash ./script/automated_sonar_analysis/run_analysis.sh -s ``` ### Custom Execution with Flags -You can provide flags to customize the execution. For example: +You can provide flags to customize the execution. In this case, it will skip the prompt and use the provided parameter. + +For example: ```bash ./script/automated_sonar_analysis/run_analysis.sh -k "custom_project_key" -n "Custom Project Name" -p "new_password" -d "/path/to/codebase" From ffa2424450c1ac483f592692695993e564f94841 Mon Sep 17 00:00:00 2001 From: IhsenBouallegue Date: Thu, 12 Sep 2024 14:55:06 +0200 Subject: [PATCH 40/40] doc --- gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md | 2 -- script/automated_sonar_analysis/README.md | 2 -- 2 files changed, 4 deletions(-) diff --git a/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md index 99fdf3a510..1ee2c49652 100644 --- a/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md +++ b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md @@ -78,8 +78,6 @@ No need to pass anything the script will prompt as needed. ### Skip Prompts -The script will use the defaults and will not prompt at all. - ```bash ./script/automated_sonar_analysis/run_analysis.sh -s ``` diff --git a/script/automated_sonar_analysis/README.md b/script/automated_sonar_analysis/README.md index 852a1b6a6b..cc9748a355 100644 --- a/script/automated_sonar_analysis/README.md +++ b/script/automated_sonar_analysis/README.md @@ -69,8 +69,6 @@ No need to pass anything the script will prompt as needed. ### Skip Prompts -The script will use the defaults and will not prompt at all. - ```bash ./script/automated_sonar_analysis/run_analysis.sh -s ```