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..1ee2c49652 --- /dev/null +++ b/gh-pages/_posts/how-to/2024-09-05-automated-sonar-analysis.md @@ -0,0 +1,105 @@ +--- +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. After execution, the script will print a reusable command with the provided configurations, which you can use next time to skip prompts. + +## 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. **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. + - Clean up the previous SonarQube project. + - Revoke existing token. + - Create a new SonarQube project and generate a token. + - Run SonarScanner for code analysis. + - Perform CodeCharta analysis. + - Run final cleanup. + +## Usage + +### 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 + +```bash +./script/automated_sonar_analysis/run_analysis.sh -s +``` + +### Custom Execution with Flags + +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" +``` + +### 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. diff --git a/script/automated_sonar_analysis/README.md b/script/automated_sonar_analysis/README.md new file mode 100644 index 0000000000..cc9748a355 --- /dev/null +++ b/script/automated_sonar_analysis/README.md @@ -0,0 +1,96 @@ +# 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. After execution, the script will print a reusable command with the provided configurations, which you can use next time to skip prompts. + +## 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. **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. + - Clean up the previous SonarQube project. + - Revoke existing token. + - Create a new SonarQube project and generate a token. + - Run SonarScanner for code analysis. + - Perform CodeCharta analysis. + - Run final cleanup. + +## Usage + +### 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 + +```bash +./script/automated_sonar_analysis/run_analysis.sh -s +``` + +### Custom Execution with Flags + +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" +``` + +### 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. diff --git a/script/automated_sonar_analysis/analysers.sh b/script/automated_sonar_analysis/analysers.sh new file mode 100644 index 0000000000..048e4bcce2 --- /dev/null +++ b/script/automated_sonar_analysis/analysers.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# 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" \ + -w /usr/src \ + sonarsource/sonar-scanner-cli \ + sonar-scanner \ + -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." + + 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 + + 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 -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 + + sleep "$interval" + waited=$((waited + interval)) + done +} + + +# Run CodeCharta analysis using docker run +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" \ + -w "$PROJECT_BASEDIR" \ + 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 + fi + + echo "βœ… CodeCharta analysis complete. Output stored in $PROJECT_BASEDIR/sonar.cc.json.gz" +} diff --git a/script/automated_sonar_analysis/cleanup.sh b/script/automated_sonar_analysis/cleanup.sh new file mode 100644 index 0000000000..2f1f339f89 --- /dev/null +++ b/script/automated_sonar_analysis/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/automated_sonar_analysis/dependency_checker.sh b/script/automated_sonar_analysis/dependency_checker.sh new file mode 100644 index 0000000000..275c7e871e --- /dev/null +++ b/script/automated_sonar_analysis/dependency_checker.sh @@ -0,0 +1,160 @@ +#!/bin/bash + +# Function to check if a command exists +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() { + 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() { + local install_command="brew install jq" + prompt_install "jq on macOS" "$install_command" +} + +# 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." + 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 +} + +# 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 + 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 check if images exist locally and pull only if needed +check_docker_images() { + 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 + if [[ $REPLY =~ ^[Nn]$ ]]; then + echo "🚫 Image pulling canceled. The script cannot proceed without these images." + exit 1 + else + for img in "${missing_images[@]}"; do + docker_pull_image "$img" + done + fi +} + +check_dependencies () { + # Run the checks for jq and Docker + check_jq + check_docker + check_docker_daemon + + # 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/helpers.sh b/script/automated_sonar_analysis/helpers.sh new file mode 100644 index 0000000000..3f9add4e59 --- /dev/null +++ b/script/automated_sonar_analysis/helpers.sh @@ -0,0 +1,257 @@ +#!/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 -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 \ + -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 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 "$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" + +} + +urlencode() { + local raw_str="$1" + local encoded_str + encoded_str=$(jq -rn --arg v "$raw_str" '$v|@uri') + echo "$encoded_str" +} + +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 +} + +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 + + 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 + printf -- "-> \e[1;32m[βœ”] ${options[i]}\e[0m\n" # Bold green for both hovered and selected + else + # Hovered option (not selected) + printf -- "-> \e[1;33m[ ] ${options[i]}\e[0m\n" # Bold yellow for hovered + fi + else + if [[ "${selected[i]}" == "1" ]]; then + # Selected option (not hovered) + printf -- " \e[32m[βœ”] ${options[i]}\e[0m\n" # Regular green for selected + else + # Neither hovered nor selected + printf -- " [ ] ${options[i]}\n" # 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 new file mode 100644 index 0000000000..a61fa58f1d --- /dev/null +++ b/script/automated_sonar_analysis/run_analysis.sh @@ -0,0 +1,220 @@ +#!/bin/bash + +### Import helper functions and scripts ####################################### + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi + +. "$DIR/dependency_checker.sh" +. "$DIR/helpers.sh" +. "$DIR/cleanup.sh" +. "$DIR/sonarqube_management.sh" +. "$DIR/analysers.sh" + +### Default Configuration ##################################################### + +# Default values for various variables +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 + +# 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 ############################################################### + +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 "------------------------------------------------------------\n" + + +# 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 + + if [ "$FLAG_PROJECT_NAME" = false ]; then + read -p "πŸ“› Enter the Project Name (default: $PROJECT_NAME): " input + PROJECT_NAME=${input:-$PROJECT_NAME} + fi + + 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 + + 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") + +preselected_indices=(0 1 2 3 4 5 6 7) # By default, all steps are preselected + +# Call the `show_menu` function from helpers.sh +show_menu "${#steps[@]}" "${steps[@]}" "${#preselected_indices[@]}" "${preselected_indices[@]}" + +# 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 + +### Run the steps ############################################################# + +check_dependencies + +# 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 + +### Print Reusable Command #################################################### + +echo -e "\nTo run this script again without prompts, use the following command:" +echo "$cmd" diff --git a/script/automated_sonar_analysis/sonarqube_management.sh b/script/automated_sonar_analysis/sonarqube_management.sh new file mode 100644 index 0000000000..0aaf6d2dc0 --- /dev/null +++ b/script/automated_sonar_analysis/sonarqube_management.sh @@ -0,0 +1,215 @@ +#!/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 +} + +ensure_sonarqube_running() { + 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." + wait_for_sonarqube_ready + 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 + wait_for_sonarqube_ready + return + fi + fi + + echo "πŸš€ Starting SonarQube container..." + docker run -d --name $SONAR_CONTAINER_NAME --network $NETWORK_NAME -p 9000:9000 sonarqube:community + + wait_for_sonarqube_ready +} + +wait_for_sonarqube_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 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 -n1) + response_body=$(echo "$response" | head -n1) + + sonarqube_status=$(echo "$response_body" | jq -r '.status') + + if [ "$sonarqube_status" == "UP" ]; then + stop_spinner "$spinner_pid" # Stop spinner if SonarQube is ready + echo -e "\nβœ… SonarQube is ready!" # Green success message + return + fi + + sleep $check_interval + elapsed_time=$((elapsed_time + check_interval)) + done + + # 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'" + + # 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 -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 + is_valid=$(echo "$response_body" | jq -r '.valid') + + 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 "\n%{http_code}" "$HOST_SONAR_URL/api/authentication/validate") + + 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') + + 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 +} + +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" \ + "$HOST_SONAR_URL/api/users/change_password") + + 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." + SONAR_USER=$DEFAULT_SONAR_USER + SONAR_PASSWORD=$NEW_SONAR_PASSWORD + else + echo "❌ Failed to change the password. HTTP status code: $http_status" + exit 1 + fi +} + + +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=$ENCODED_PROJECT_KEY") + 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 + 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() { + 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") + 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 + 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 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 -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." + 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=$ENCODED_PROJECT_KEY&name=$ENCODED_PROJECT_NAME") + 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." + echo "Response: $response_body" + exit 1 + fi + + check_response $http_status "$response_body" "Project creation failed." + echo "βœ… Project created successfully." +}