diff --git a/README.md b/README.md index 75f2307..4196979 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Directory | Usage | `reports` | Controls the stuff related to look and feel of the generated reports.
i)`templating.py` - Code containing the structure of the report.
ii) `utils` - Code / Utility functions essential for the report templating to be up and running. | `utils` | General Utility Functions. `tests` | Contains the Unit Tests. | +`ascii_art` | Contains the logic for the Welcome Banner. | Files | Usage | --------------------------|---------------------------------------| @@ -66,3 +67,24 @@ redcoffee generatepdf --host=${YOUR_SONARQUBE_HOST_NAME} --project=${SONARQUBE_P Please visit the Github Page for this project to stay updated with the latest changes - [Github Page Documentation for RedCoffee](https://anubhav9.github.io/RedCoffee) +## Latest Updates + +Updates as of June 29,2025 - `v2.16` + +2 new commands have been added + +* `redcoffee support` - If you encounter a bug or you feel RedCoffee is not working as expected or you want to raise a new feature request, `redcoffee support` command will guide on how to reach out to the maintener of this project. + +![image](https://github.com/user-attachments/assets/019f262e-9527-499e-a05b-1b62f70ba135) + +* `redcoffee diagnose --host={host_name} --token={sonarqube_usertoken} ` - This is the basic healthcheck / sanity command. This helps you to diagnose if the required infra / configurations required for RedCoffee to be up and running are working fine or not. In case, you see something is failing in this command, I would please request to cross check the parameters being supplied. In case, it is still failing, please use the `redcoffee support` command to raise this issue to the maintener of this project + +![image](https://github.com/user-attachments/assets/340d8095-c530-4cb8-a1b0-6de5a22bb14f) + +## Coming Soon + +* Ability to email the report , right from the generate command - Users will be able to email the report to the stakeholders right from the generate command. A new flag would be added for the same. +* Support for Jenkins - While Github Actions is already supported by RedCoffee, support for Jekins was a long due. The next version should have the support for Jenkins available. + + + diff --git a/ascii_art/__init__.py b/ascii_art/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ascii_art/__init__.py @@ -0,0 +1 @@ + diff --git a/ascii_art/constants.py b/ascii_art/constants.py new file mode 100644 index 0000000..29c3ec4 --- /dev/null +++ b/ascii_art/constants.py @@ -0,0 +1,2 @@ +REDCOFFEE_BANNER_TEXT = "REDCOFFEE" +REDCOFFEE_SUB_TEXT = "\t\tCode Quality.Brewed Instantly" diff --git a/ascii_art/welcome_banner.py b/ascii_art/welcome_banner.py new file mode 100644 index 0000000..60c3bcc --- /dev/null +++ b/ascii_art/welcome_banner.py @@ -0,0 +1,17 @@ +import pyfiglet +import colorama +from ascii_art import constants + +def generate_welcome_banner(): + """ + Generate a welcome banner using pyfiglet and colorama. + """ + banner = pyfiglet.figlet_format(constants.REDCOFFEE_BANNER_TEXT) + colored_banner = colorama.Fore.RED + banner + print(colored_banner,end="") + sub_text = colorama.Fore.YELLOW + constants.REDCOFFEE_SUB_TEXT + print(sub_text) + print("\n") + print(colorama.Style.RESET_ALL) + + diff --git a/core/analyser.py b/core/analyser.py index 6bf8c5d..58c594d 100644 --- a/core/analyser.py +++ b/core/analyser.py @@ -16,7 +16,7 @@ load_dotenv() -redcoffee_current_version = "v2.15" +redcoffee_current_version = "v2.16" sentry_integration = SentryIntegration( os.environ.get("SENTRY_DSN_URL", ""), False, 1.0, 1.0) ipinfo_integration = IPInfoIntegration(os.environ.get("IP_INFO_ACCESS_TOKEN", "")) diff --git a/core/utils/sonarqube_utils.py b/core/utils/sonarqube_utils.py index 04f6603..e0f63ee 100644 --- a/core/utils/sonarqube_utils.py +++ b/core/utils/sonarqube_utils.py @@ -8,7 +8,7 @@ def get_duplication_map(host_name, project_name, auth_token, protocol): protocol_type = handle_protocol_for_every_communication( protocol, host_name) - if host_name.startswith("http") or host_name.startswith("http"): + if host_name.startswith("http") or host_name.startswith("https"): host_name = general_utils.remove_protocol(host_name) DUPLICATION_URL = f"{protocol_type}{host_name}/api/measures/component_tree?component={project_name}&metricKeys=duplicated_lines" logging.info(f"Generated Duplication URL is :: {DUPLICATION_URL}") diff --git a/diagnose/__init__.py b/diagnose/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/diagnose/__init__.py @@ -0,0 +1 @@ + diff --git a/diagnose/constants.py b/diagnose/constants.py new file mode 100644 index 0000000..7da2278 --- /dev/null +++ b/diagnose/constants.py @@ -0,0 +1,15 @@ +HEALTHCHECK_ENDPOINT = "/api/system/health" +MESSAGE_BASE_SONARQUBE_USER_TOKEN = "✅ SonarQube User Token is being passed." +MESSAGE_SONARQUBE_NOT_A_USER_TOKEN = "❌ SonarQube User Token is not being passed." +MESSAGE_SONARQUBE_USER_TOKEN_HINT_MESSAGE = "SonarQube User Token starts with 'squ_'. User Token is required to generate the report." +MESSAGE_SONARQUBE_USER_TOKEN_GENERATION_STEPS = "To generate a new SonarQube User Token, please go to your SonarQube account and then navigate to User -> My Account -> Security -> Generate New Token and then select SonarQube User Token." +MESSAGE_SONARQUBE_SERVER_REACHABLE = "✅ SonarQube Server is reachable." +MESSAGE_SONARQUBE_SERVER_UNREACHABLE = "❌ SonarQube Server is unreachable." +MESSAGE_SONARQUBE_SERVER_UNREACHABLE_HINT_MESSAGE = "Please check if the SonarQube Server is reachable and if you have supplied the correct protocol and host. Sometimes VPNs can cause the issue of SonarQube Server being unreachable , so please try to connect to the SonarQube Server without VPN if possible" +MESSAGE_USER_TOKEN_VALIDATED = "✅ SonarQube User Token is valid." +MESSAGE_USER_TOKEN_SOMETHING_WRONG = "❌ Something went wrong while validating the SonarQube User Token. Please try again with a valid token." +MESSAGE_USER_TOKEN_401 = "❌ SonarQube User Token is invalid. We are getting response code 401 which validates that you are Unauthorised to access the SonarQube Server. Please try again with a valid token." +MESSAGE_USER_TOKEN_403 = "❌ SonarQube User Token is invalid. We are getting response code 403 which validates that you are Forbidden to access the SonarQube Server. Please try again with a valid token." +MESSAGE_SUPPORT_APOLOGIES = "🙂‍↕️ Please accept my heartiest apologies if RedCoffee is not working as expected for you. As a sole maintainer of this project, I am trying my best to make it work for everyone. If you are facing any issue, please raise an issue on our GitHub repository - https://github.com/Anubhav9/redcoffee/issues" +MESSAGE_SUPPORT_DIAGNOSE_COMMAND = "❓ Did we check the redcoffee diagnose command? If not, please run the command again and check if the issue persists. If it does, please raise an issue on our GitHub repository" +MESSAGE_SUPPORT_EMAIL = "✍️ Still not sure what's happening? Please shoot an email to anubhavsanyal9@gmail.com" diff --git a/diagnose/sanity.py b/diagnose/sanity.py new file mode 100644 index 0000000..77971f9 --- /dev/null +++ b/diagnose/sanity.py @@ -0,0 +1,62 @@ +from diagnose import constants +import requests +from utils import general_utils + +def check_all_functioning_parameters(protocol,host,token): + """ + Check if all the parameters are functioning correctly. + """ + total_checks = 3 + successful_checks = 0 + unsuccessful_checks = 3 + confirm_if_user_token = token.startswith("squ_") + if confirm_if_user_token: + print(constants.MESSAGE_BASE_SONARQUBE_USER_TOKEN) + successful_checks=successful_checks+1 + else: + + print(constants.MESSAGE_SONARQUBE_NOT_A_USER_TOKEN) + print(constants.MESSAGE_SONARQUBE_USER_TOKEN_HINT_MESSAGE) + print(constants.MESSAGE_SONARQUBE_USER_TOKEN_GENERATION_STEPS) + + try: + healthcheck_response= requests.get(f"{protocol}{host}{constants.HEALTHCHECK_ENDPOINT}",auth=(token,"")) + if healthcheck_response.status_code == 200: + status = healthcheck_response.json()["health"] + if status == "GREEN" or status == "YELLOW": + successful_checks=successful_checks+1 + print(constants.MESSAGE_SONARQUBE_SERVER_REACHABLE) + authenticate_token = requests.get(f"{protocol}{host}/api/authentication/validate",auth=(token,"")) + if authenticate_token.status_code == 200: + token_result = authenticate_token.json()["valid"] + if token_result == True: + successful_checks=successful_checks+1 + print(constants.MESSAGE_USER_TOKEN_VALIDATED) + else: + print(constants.MESSAGE_USER_TOKEN_SOMETHING_WRONG) + else: + authentication_status_code = authenticate_token.status_code + if authentication_status_code == 401: + print(constants.MESSAGE_USER_TOKEN_401) + elif authentication_status_code == 403: + print(constants.MESSAGE_USER_TOKEN_403) + else: + print(constants.MESSAGE_USER_TOKEN_SOMETHING_WRONG) + else: + print(constants.MESSAGE_SONARQUBE_SERVER_UNREACHABLE) + print(constants.MESSAGE_SONARQUBE_SERVER_UNREACHABLE_HINT_MESSAGE) + + print(f"Final Diagnosis: {successful_checks} out of {total_checks} checks passed.") + except Exception as e: + print(constants.MESSAGE_SONARQUBE_SERVER_UNREACHABLE) + print(constants.MESSAGE_SONARQUBE_SERVER_UNREACHABLE_HINT_MESSAGE) + print(f"Diagnosis Failed. Only able to validate SonarQube User Token. Passed {successful_checks} and rest cannot be validated") + + +def connect_for_support(): + """ + Connect for support. Opening Github Issues or shooting an email + """ + print(constants.MESSAGE_SUPPORT_APOLOGIES) + print(constants.MESSAGE_SUPPORT_DIAGNOSE_COMMAND) + print(constants.MESSAGE_SUPPORT_EMAIL) diff --git a/docs/changelog.md b/docs/changelog.md index ae813b6..b23d36b 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,13 @@ ## Change Log for RedCoffee +### Version 2.16 ( Released on June 29,2025): +* Inclusion of a new command : `redcoffee diagnose` - This does a sanity check whether the required infra/parameters needed by RedCoffee is up and running or not. This checks for SonarQube Connectivity and validity / authenticity of the provided SonarQube Token (which ideally should be the SonarQube user token). The user can get a hint of what is wrong with RedCoffee post running this command. +* Inclusion of a new command : `redcoffee support`- This encourages the end user to create a Github Issue in case they feel something is wrong with RedCoffee and needs an urgent attention. Alternatively, the maintener's email has been given as well , incase , someone wants to reach out directly. +* Addition of a new ASCII Banner. Users will be greeted with this ASCII art when they run any commands of RedCoffee. +* Bug Fix - Saw errors on Sentry regarding Invalid Path Supplied. This was happening because when no path was supplied, RedCoffee was trying to create reports in Downloads Directory. However, we were not checking if Downloads directory exists or not. Added the code to create a new directory called `redcoffee-reports` where RedCoffee reports will be stored in case user does not supply the required path. +* Minor Bug Fix - An if condition in the code was incorrect. This was however not creating any issues. But fixed it just in case. + + ### Version 2.15 ( Released on May 25,2025 ) * No Changes from a Customer POV. The Code / Repository structure has been modified to segregate components and follow the Single Responsibility Principle. * Better adherence to PEP8 structure. diff --git a/docs/documentation.md b/docs/documentation.md index 8ba4a9d..609a7d1 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -48,3 +48,11 @@ In this case as well, the protocol flag will take the priority. Please use the below command
redcoffee generatepdf --host=${YOUR_SONARQUBE_HOST_NAME} --project=${SONARQUBE_PROJECT_KEY} --path=${PATH WHERE PDF FILE IS TO BE STORED} --token=${SONARQUBE_USER_TOKEN}
+ +### Latest Command addition in Version 2.16 and above + +Pleased to introduce 2 new commands from v2.16 and onwards + +`redcoffee diagnose --host={host_name} --token={sonarqube_user_token}` - This is the healthcheck / sanity command. This makes sures that all the required infra / configurations needed for RedCoffee to be up and running are working fine. If you see something failing over here, its better to cross check the values being supplied. + +`redcoffee support` - This is the support command. After running the `redcoffee diagnose` command, if you still could not figure out whats wrong or you need additional support or you might have figured out a bug or you want to request for a new feature, this command provides you with instruction on how to raise it to the maintainer. diff --git a/redcoffee.py b/redcoffee.py index ab39b50..2577992 100644 --- a/redcoffee.py +++ b/redcoffee.py @@ -3,6 +3,9 @@ from dotenv import load_dotenv from utils import general_utils from core import analyser +from diagnose import sanity +from ascii_art import welcome_banner + load_dotenv() @@ -20,6 +23,7 @@ def cli(): @click.option("--protocol", type=click.Choice(["http", "https"], case_sensitive=False), required=False, help="The protocol that you want to enforce - HTTP or HTTPS") def generatepdf(host, project, path, token, protocol): + welcome_banner.generate_welcome_banner() resolved_path = str(general_utils.check_and_validate_file_path(path)) analyser.generate_final_report_and_transmit_to_sentry( resolved_path, host, project, token, protocol) @@ -27,7 +31,27 @@ def generatepdf(host, project, path, token, protocol): if path != resolved_path: print(warning_for_path_change(resolved_path)) +@click.command() +@click.option("--protocol", type=click.Choice(["http", "https"], case_sensitive=False), required=False, + help="The protocol that you want to enforce - HTTP or HTTPS",default=None) +@click.option("--host", help="The host url where SonarQube server is running", required=True) +@click.option("--token", help="SonarQube Global Analysis Token", required=True) +def diagnose(protocol,host,token): + welcome_banner.generate_welcome_banner() + protocol = general_utils.handle_protocol_for_every_communication( + protocol, host) + if host.startswith("http") or host.startswith("https"): + host = general_utils.remove_protocol(host) + sanity.check_all_functioning_parameters(protocol,host,token) + +@click.command() +def support(): + welcome_banner.generate_welcome_banner() + sanity.connect_for_support() + cli.add_command(generatepdf) +cli.add_command(diagnose) +cli.add_command(support) if __name__ == "__main__": cli() diff --git a/requirements.txt b/requirements.txt index 51f5a3e..2ed201a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,5 @@ sentry_sdk==2.22.0 setuptools==75.1.0 python-dotenv mkdocs +pyfiglet +colorama diff --git a/setup.py b/setup.py index 52c501d..10d173a 100644 --- a/setup.py +++ b/setup.py @@ -11,14 +11,14 @@ setup( name='redcoffee', - version='2.15', + version='2.16', author="Anubhav Sanyal", description='A command-line tool to generate PDF for SonarQube Reports', long_description=README, long_description_content_type='text/markdown', # Change this if your README is not markdown packages=find_packages(), # Automatically find packages - py_modules=['redcoffee', 'styling', 'constants','support'], + py_modules=['redcoffee', 'styling', 'constants','support','ascii_art','diagnose'], install_requires=[ 'click', 'reportlab', @@ -27,7 +27,9 @@ 'setuptools', 'ipinfo', 'sentry_sdk', - 'python-dotenv' + 'python-dotenv', + 'pyfiglet', + 'colorama' ], entry_points=''' [console_scripts] diff --git a/tests/unit_test.py b/tests/unit_test.py index e52e348..5447290 100644 --- a/tests/unit_test.py +++ b/tests/unit_test.py @@ -63,7 +63,7 @@ def test_path_validation(path_name, case): if case == "file_name_wit_pdf": assert returned_result == "car-loan-report.pdf", "Mismatch in Path found" elif case == "invalid_directory_name" or case == "invalid_directory_name_ending_with_pdf": - resolved_path = Path.home() / "Downloads" / "generated-sonarqube-report.pdf" + resolved_path = Path.home() / "redcoffee-reports" / "generated-sonarqube-report.pdf" assert returned_result == resolved_path, "Mismatch in Path found" elif case == "random": print("Hello from Alaska"+ str(Path.home())) diff --git a/utils/general_utils.py b/utils/general_utils.py index 82a2e6a..dbbb030 100644 --- a/utils/general_utils.py +++ b/utils/general_utils.py @@ -2,7 +2,7 @@ from pathlib import Path from urllib.parse import urlparse import constants - +import os def check_and_validate_file_path(path): """ @@ -12,7 +12,8 @@ def check_and_validate_file_path(path): path: The file path being provided by the user """ if path is None: - resolved_directory = Path.home() / "Downloads" / constants.FALLBACK_FILE_NAME + create_redcoffee_report_directory() + resolved_directory = Path.home() / "redcoffee-reports" / constants.FALLBACK_FILE_NAME return resolved_directory else: @@ -30,9 +31,8 @@ def check_and_validate_file_path(path): resolved_directory = path return resolved_directory else: - logging.info( - "Path does not exists, we will fallback to defaults") - resolved_directory = Path.home() / "Downloads" / constants.FALLBACK_FILE_NAME + create_redcoffee_report_directory() + resolved_directory = Path.home() / "redcoffee-reports" / constants.FALLBACK_FILE_NAME return resolved_directory @@ -72,3 +72,15 @@ def remove_protocol(url): """ parsed_url = urlparse(url) return parsed_url.netloc + parsed_url.path + + +def create_redcoffee_report_directory(): + """ + Create the redcoffee-reports directory + """ + try: + os.makedirs(os.path.join(Path.home(), "redcoffee-reports"), exist_ok=True) + except Exception as e: + logging.info(f"Error creating redcoffee-reports directory: {e}") + +