├── .github ├── dependabot.yml └── workflows │ ├── codacy.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── SECURITY.md ├── action.yml ├── install.sh └── makefile.sh /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/codacy.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # This workflow checks out code, performs a Codacy security scan 7 | # and integrates the results with the 8 | # GitHub Advanced Security code scanning feature. For more information on 9 | # the Codacy security scan action usage and parameters, see 10 | # https://github.com/codacy/codacy-analysis-cli-action. 11 | # For more information on Codacy Analysis CLI in general, see 12 | # https://github.com/codacy/codacy-analysis-cli. 13 | 14 | name: Codacy Security Scan 15 | 16 | on: 17 | push: 18 | branches: [ "main" ] 19 | pull_request: 20 | # The branches below must be a subset of the branches above 21 | branches: [ "main" ] 22 | schedule: 23 | - cron: '55 23 * * 5' # Runs at 23:55 every Friday 24 | 25 | permissions: 26 | contents: read 27 | 28 | jobs: 29 | codacy-security-scan: 30 | permissions: 31 | contents: read # for actions/checkout to fetch code 32 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 33 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 34 | name: Codacy Security Scan 35 | runs-on: ubuntu-latest 36 | steps: 37 | # Checkout the repository to the GitHub Actions runner 38 | - name: Checkout code 39 | uses: actions/checkout@v4 40 | 41 | # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis 42 | - name: Run Codacy Analysis CLI 43 | uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b 44 | with: 45 | # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository 46 | # You can also omit the token and run the tools that support default configurations 47 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 48 | verbose: true 49 | output: results.sarif 50 | format: sarif 51 | # Adjust severity of non-security issues 52 | gh-code-scanning-compat: true 53 | # Force 0 exit code to allow SARIF file generation 54 | # This will handover control about PR rejection to the GitHub side 55 | max-allowed-issues: 2147483647 56 | 57 | # Upload the SARIF file generated in the previous step 58 | - name: Upload SARIF results file 59 | uses: github/codeql-action/upload-sarif@v2 60 | with: 61 | sarif_file: results.sarif 62 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - releases/* 9 | 10 | schedule: 11 | - cron: '55 23 * * 5' # Runs at 23:55 every Friday 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | sonarless-cli-test: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Sonarless install test - from internet 24 | run: | 25 | cp ./install.sh /tmp 26 | cd /tmp 27 | cat ./install.sh | bash 28 | 29 | - name: Check Install 30 | run: | 31 | grep "sonarless" ~/.bashrc 32 | ls -lah $HOME/.sonarless/makefile.sh 33 | $HOME/.sonarless/makefile.sh help 34 | 35 | sonarless-cli-test-from-git: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | 41 | - name: Sonarless install test - from git 42 | run: | 43 | cat ./install.sh | bash 44 | 45 | - name: Check Install 46 | run: | 47 | grep "sonarless" ~/.bashrc 48 | ls -lah $HOME/.sonarless/makefile.sh 49 | $HOME/.sonarless/makefile.sh help 50 | 51 | - name: Sonarless uninstall test 52 | run: | 53 | $HOME/.sonarless/makefile.sh uninstall 54 | 55 | - name: Check Uninstall 56 | run: | 57 | if [[ -d "$HOME/.sonarless" ]]; then 58 | echo "sonarless scriptlet is not uninstalled - bad" 59 | exit 1 60 | else 61 | echo "sonarless scriptlet removed - good" 62 | fi 63 | 64 | sonarless-action-test: 65 | runs-on: ubuntu-latest 66 | steps: 67 | - name: Checkout 68 | uses: actions/checkout@v4 69 | 70 | - name: Sonarless Scan 71 | uses: ./ 72 | with: 73 | sonar-source-path: '.' 74 | sonar-metrics-path: './blahblah.json' 75 | sonar-instance-port: '9999' 76 | 77 | - name: Check Sonar Metrics 78 | run: | 79 | echo "Checking for 0 vulnerabilities in Sonar Metrics JSON" 80 | VULN=$(cat ./blahblah.json | jq -r '.component.measures[] | select(.metric == "vulnerabilities").value') 81 | echo "# of vulnerabilities = ${VULN}" 82 | [ "${VULN}" -eq "0" ] 83 | 84 | echo "Checking for any issues <= 3 in Sonar Metrics JSON" 85 | ISSUES=$(cat ./blahblah.json | jq -r '.component.measures[] | select(.metric == "open_issues").value') 86 | echo "# of issues = ${ISSUES}" 87 | [ "${ISSUES}" -le 3 ] 88 | 89 | sonarless-action-test-on-main: 90 | runs-on: ubuntu-latest 91 | steps: 92 | - name: Checkout 93 | uses: actions/checkout@v4 94 | 95 | - name: Sonarless Scan 96 | uses: gitricko/sonarless@main 97 | with: 98 | sonar-source-path: '.' 99 | sonar-metrics-path: './blahblah.json' 100 | sonar-instance-port: '9999' 101 | 102 | - name: Check Sonar Metrics 103 | run: | 104 | echo "Checking for 0 vulnerabilities in Sonar Metrics JSON" 105 | VULN=$(cat ./blahblah.json | jq -r '.component.measures[] | select(.metric == "vulnerabilities").value') 106 | echo "# of vulnerabilities = ${VULN}" 107 | [ "${VULN}" -eq "0" ] 108 | 109 | echo "Checking for any issues <= 3 in Sonar Metrics JSON" 110 | ISSUES=$(cat ./blahblah.json | jq -r '.component.measures[] | select(.metric == "open_issues").value') 111 | echo "# of issues = ${ISSUES}" 112 | [ "${ISSUES}" -le 3 ] 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Sonarless 2 | sonar-metrics.json 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional stylelint cache 61 | .stylelintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # act 94 | bin/** 95 | 96 | # ncc 97 | lib 98 | 99 | # Nuxt.js build / generate output 100 | .nuxt 101 | # dist 102 | 103 | # Gatsby files 104 | .cache/ 105 | # Comment in the public line in if your project uses Gatsby and not Next.js 106 | # https://nextjs.org/blog/next-9-1#public-directory-support 107 | # public 108 | 109 | # vuepress build output 110 | .vuepress/dist 111 | 112 | # vuepress v2.x temp and cache directory 113 | .temp 114 | .cache 115 | 116 | # Docusaurus cache and generated files 117 | .docusaurus 118 | 119 | # Serverless directories 120 | .serverless/ 121 | 122 | # FuseBox cache 123 | .fusebox/ 124 | 125 | # DynamoDB Local files 126 | .dynamodb/ 127 | 128 | # TernJS port file 129 | .tern-port 130 | 131 | # Stores VSCode versions used for testing VSCode extensions 132 | .vscode-test 133 | 134 | # yarn v2 135 | .yarn/cache 136 | .yarn/unplugged 137 | .yarn/build-state.yml 138 | .yarn/install-state.gz 139 | .pnp.* 140 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 gitricko and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Test](https://github.com/gitricko/sonarless/actions/workflows/test.yml/badge.svg)](https://github.com/gitricko/sonarless/actions/workflows/test.yml) 2 | [![Codacy](https://github.com/gitricko/sonarless/actions/workflows/codacy.yml/badge.svg)](https://github.com/gitricko/sonarless/actions/workflows/codacy.yml) 3 | ![GitHub License](https://img.shields.io/github/license/gitricko/sonarless) 4 | ![GitHub Release](https://img.shields.io/github/v/release/gitricko/sonarless) 5 | ![GitHub commits since latest release](https://img.shields.io/github/commits-since/gitricko/sonarless/latest) 6 | 7 | # Sonarless v1.3 8 | 9 | This developer-friendly CLI and GitHub Action enable SonarQube scanning for your repository without the need for a dedicated hosted SonarQube server. It starts a SonarQube Docker instance, allowing developers to scan code, check results, and generate a JSON metrics file for automation. This ensures you can easily assess and maintain the quality of your code. 10 | 11 | # What's new 12 | 13 | Please refer to the [release page](https://github.com/gitricko/sonarless/releases/latest) for the latest release notes. 14 | 15 | # Use Sonarless in your Local Dev 16 | 17 | To install CLI, paste and run the following in a terminal: 18 | > `curl -s "https://raw.githubusercontent.com/gitricko/sonarless/main/install.sh" | bash` 19 | 20 | ```ssh 21 | _ 22 | ___ ___ _ __ __ _ _ __ | | ___ ___ ___ 23 | / __| / _ \ | "_ \ / _` || "__|| | / _ \/ __|/ __| 24 | \__ \| (_) || | | || (_| || | | || __/\__ \\__ \ 25 | |___/ \___/ |_| |_| \__,_||_| |_| \___||___/|___/ 26 | 27 | 28 | Now attempting installation... 29 | 30 | Looking for a previous installation of SONARLESS... 31 | Looking for docker... 32 | Looking for jq... 33 | Looking for sed... 34 | Installing Sonarless helper scripts... 35 | * Downloading... 36 | 37 | ######################################################################## 100.0% 38 | 39 | Please open a new terminal, or run the following in the existing one: 40 | 41 | alias sonarless='/home/runner/.sonarless/makefile.sh' 42 | 43 | Then issue the following command: 44 | 45 | sonarless help 46 | 47 | Enjoy!!! 48 | ``` 49 | To understand CLI sub-commands, just run `sonarless help` 50 | 51 | Usually, you only need to know 2 sub-commands 52 | - `sonarless scan`: to start scanning your code in the current directory will be uploaded for scanning. When the scan is done, just login webui into your local personal instance of sonarqube via [http://localhost:9234](http://localhost:9234) to get details from SonarQube. The default password for `admin` is `Son@rless123` 53 | 54 | - `sonarless results`: to generate `sonar-metrics.json` metrics file in your current directory 55 | 56 | To clean up your sonar instance, just run `sonarless docker-clean`. SonarQube docker instance will be stop and all images removed. 57 | 58 | This CLI works perfectly with Github CodeSpace 59 | 60 | 61 | # GitHub Action Usage 62 | 63 | 64 | ```yaml 65 | - uses: gitricko/sonarless@v1.3 66 | with: 67 | # Folder path to scan from git-root 68 | # Default: . 69 | sonar-source-path: '' 70 | 71 | # Path to SonarQube metrics json from git-root 72 | # Default: ./sonar-metrics.json 73 | sonar-metrics-path: '' 74 | 75 | # SonarQube Project Name 76 | # Default: ${{ github.event.repository.name }} 77 | sonar-project-name: '' 78 | 79 | # SonarQube Project Key 80 | # Default: ${{ github.event.repository.name }} 81 | sonar-project-key: '' 82 | ``` 83 | 84 | 85 | # Scenarios 86 | 87 | - [Scan all files from git root directory](#Sonar-scan-all-files-from-git-root-directory) 88 | - [Scan particular folder from git root directory](#Scan-particular-folder-from-git-root-directory) 89 | - [Scan code and fail build if metrics is below expectation](#Scan-code-and-fail-build-if-metrics-is-below-expectation) 90 | - [Options to change local sonarqube server port](#Options-to-change-local-sonarqube-server-port) 91 | 92 | ## Sonar scan all files from git root directory 93 | 94 | ```yaml 95 | jobs: 96 | Sonarless-Scan: 97 | runs-on: ubuntu-latest 98 | steps: 99 | - name: Checkout repository 100 | uses: actions/checkout@v4 101 | 102 | - name: Sonarless Scan 103 | uses: gitricko/sonarless@v1.3 104 | ``` 105 | 106 | ## Scan particular folder from git root directory 107 | 108 | ```yaml 109 | jobs: 110 | Sonarless-Scan: 111 | runs-on: ubuntu-latest 112 | steps: 113 | - name: Checkout repository 114 | uses: actions/checkout@v4 115 | 116 | - name: Sonarless Scan 117 | uses: gitricko/sonarless@v1.3 118 | with: 119 | sonar-source-path: 'src' 120 | ``` 121 | 122 | ## Scan code and fail build if metrics is below expectation 123 | 124 | ```yaml 125 | jobs: 126 | Sonarless-Scan: 127 | runs-on: ubuntu-latest 128 | steps: 129 | - name: Checkout repository 130 | uses: actions/checkout@v4 131 | 132 | - name: Sonarless Scan 133 | uses: gitricko/sonarless@v1.3 134 | with: 135 | sonar-source-path: 'src' 136 | sonar-metrics-path: './sonar-mymetrics.json' 137 | 138 | - name: Check Sonar Metrics - No Vulnerabilities 139 | run: | 140 | echo "Checking for any vulnerabilities in Sonar Metrics JSON" 141 | VULN=$(cat ./sonar-mymetrics.json | jq -r '.component.measures[] | select(.metric == "vulnerabilities").value') 142 | echo "# of vulnerabilities = ${VULN}" 143 | [ ${VULN} -eq "0" ] 144 | ``` 145 | 146 | ## Options to change local sonarqube server port 147 | Just in case your local machine/GHA container need to use the default port of `9234` 148 | ```yaml 149 | jobs: 150 | Sonarless-Scan: 151 | runs-on: ubuntu-latest 152 | steps: 153 | - name: Checkout repository 154 | uses: actions/checkout@v4 155 | 156 | - name: Sonarless Scan 157 | uses: gitricko/sonarless@v1.3 158 | with: 159 | sonar-instance-port: '1234' 160 | ``` 161 | 162 | # Coffee 163 | 164 | If you find this small helper script and action helpful, buy me a [sip of coffee](https://ko-fi.com/gitricko) here to show your appreciation (only if you want to) 165 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Sonarless Code Scan" 2 | description: "SonarQube Scan GitHub Action without a dedicated hosted SonarQube Server" 3 | author: "gitricko" 4 | 5 | inputs: 6 | sonar-project-name: 7 | description: "SonarQube Project Name" 8 | required: false 9 | default: ${{github.event.repository.name}} 10 | sonar-project-key: 11 | description: "SonarQube Project Key" 12 | required: false 13 | default: ${{github.event.repository.name}} 14 | sonar-source-path: 15 | description: "SonarQube Source Path from Git Root" 16 | required: false 17 | sonar-metrics-path: 18 | description: "SonarQube Metrics JSON Path from Git Root" 19 | required: false 20 | sonar-instance-port: 21 | description: "SonarQube Instance Port" 22 | required: false 23 | 24 | runs: 25 | using: "composite" 26 | steps: 27 | - name: Get Docker Deps 28 | run: | 29 | ${{ github.action_path }}/makefile.sh docker-deps-get 30 | ${{ github.action_path }}/makefile.sh sonar-ext-get 31 | shell: bash 32 | 33 | - name: Scanning 34 | run: ${{ github.action_path }}/makefile.sh scan 35 | shell: bash 36 | env: 37 | SONAR_PROJECT_NAME: ${{inputs.sonar-project-name}} 38 | SONAR_PROJECT_KEY: ${{inputs.sonar-project-key}} 39 | SONAR_SOURCE_PATH: ${{inputs.sonar-source-path}} 40 | SONAR_METRICS_PATH: ${{inputs.sonar-metrics-path}} 41 | SONAR_INSTANCE_PORT: ${{ inputs.sonar-instance-port }} 42 | SONAR_GITROOT: ${{ github.workspace }} 43 | 44 | - name: Scan Results 45 | run: ${{ github.action_path }}/makefile.sh results 46 | shell: bash 47 | env: 48 | SONAR_PROJECT_NAME: ${{inputs.sonar-project-name}} 49 | SONAR_PROJECT_KEY: ${{inputs.sonar-project-key}} 50 | SONAR_SOURCE_PATH: ${{inputs.sonar-source-path}} 51 | SONAR_METRICS_PATH: ${{inputs.sonar-metrics-path}} 52 | SONAR_INSTANCE_PORT: ${{ inputs.sonar-instance-port }} 53 | SONAR_GITROOT: ${{ github.workspace }} 54 | 55 | branding: 56 | icon: "check-circle" 57 | color: "green" 58 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2024 gitricko 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | set -e 19 | 20 | track_last_command() { 21 | last_command=$current_command 22 | current_command=$BASH_COMMAND 23 | } 24 | trap track_last_command DEBUG 25 | 26 | echo_failed_command() { 27 | local exit_code="$?" 28 | if [[ "$exit_code" != "0" ]]; then 29 | echo "'$last_command': command failed with exit code $exit_code." 30 | fi 31 | } 32 | trap echo_failed_command EXIT 33 | 34 | # Global variables 35 | export SONARLESS_SOURCES="https://raw.githubusercontent.com/gitricko/sonarless/main/makefile.sh" # URL where makefile.sh is hosted 36 | 37 | if [ -z "$SONARLESS_DIR" ]; then 38 | SONARLESS_DIR="$HOME/.sonarless" 39 | fi 40 | export SONARLESS_DIR 41 | 42 | # Local variables 43 | sonarless_bashrc="${HOME}/.bashrc" 44 | sonarless_zshrc="${HOME}/.zshrc" 45 | 46 | 47 | echo '' 48 | echo ' _ ' 49 | echo ' ___ ___ _ __ __ _ _ __ | | ___ ___ ___ ' 50 | echo ' / __| / _ \ | "_ \ / _` || "__|| | / _ \/ __|/ __| ' 51 | echo ' \__ \| (_) || | | || (_| || | | || __/\__ \\__ \ ' 52 | echo ' |___/ \___/ |_| |_| \__,_||_| |_| \___||___/|___/ ' 53 | echo '' 54 | echo '' 55 | echo ' Now attempting installation...' 56 | echo '' 57 | 58 | 59 | # Sanity checks 60 | 61 | echo "Looking for a previous installation of SONARLESS..." 62 | if [ -d "$SONARLESS_DIR" ]; then 63 | echo "SONARLESS found." 64 | echo "" 65 | echo "======================================================================================================" 66 | echo " You already have SONARLESS installed." 67 | echo " SONARLESS was found at:" 68 | echo "" 69 | echo " ${SONARLESS_DIR}" 70 | echo "" 71 | echo " Please consider uninstalling and reinstall." 72 | echo "" 73 | echo " $ sonarless uninstall " 74 | echo "" 75 | echo " or " 76 | echo "" 77 | echo " $ rm -rf ${SONARLESS_DIR}" 78 | echo "" 79 | echo "======================================================================================================" 80 | echo "" 81 | exit 0 82 | fi 83 | 84 | echo "Looking for docker..." 85 | if ! command -v docker > /dev/null; then 86 | echo "Not found." 87 | echo "======================================================================================================" 88 | echo " Please install docker on your system using your favourite package manager." 89 | echo "" 90 | echo " Restart after installing docker." 91 | echo "======================================================================================================" 92 | echo "" 93 | exit 1 94 | fi 95 | 96 | echo "Looking for jq..." 97 | if ! command -v jq > /dev/null; then 98 | echo "Not found." 99 | echo "======================================================================================================" 100 | echo " Please install jq on your system using your favourite package manager." 101 | echo "" 102 | echo " Restart after installing jq." 103 | echo "======================================================================================================" 104 | echo "" 105 | exit 1 106 | fi 107 | 108 | echo "Looking for sed..." 109 | if ! command -v sed > /dev/null; then 110 | echo "Not found." 111 | echo "======================================================================================================" 112 | echo " Please install sed on your system using your favourite package manager." 113 | echo "" 114 | echo " Restart after installing sed." 115 | echo "======================================================================================================" 116 | echo "" 117 | exit 1 118 | fi 119 | 120 | echo "Installing Sonarless helper scripts..." 121 | 122 | # Create directory structure 123 | mkdir -p "${SONARLESS_DIR}" 124 | 125 | set +e 126 | # Download makefile.sh depending which env (git or over curl) 127 | # Check if you are in sonarless git 128 | LOCAL_FILE_EXIST=$([[ -d ./.git ]] && git remote get-url origin | grep -q sonarless && [[ -s ./makefile.sh ]]; echo "$?") 129 | if [[ "${LOCAL_FILE_EXIST}" -eq "0" ]]; then 130 | echo "* Copying from local git..." 131 | cp -f ./makefile.sh "${SONARLESS_DIR}" 132 | else 133 | echo "* Downloading..." 134 | curl --fail --location --progress-bar "${SONARLESS_SOURCES}" > "${SONARLESS_DIR}/makefile.sh" 135 | chmod +x "${SONARLESS_DIR}/makefile.sh" 136 | fi 137 | 138 | # Create alias in ~/.bashrc ~/.zshrc if available 139 | if [[ ! -s "${sonarless_bashrc}" ]] || ! grep -q 'sonarless' "${sonarless_bashrc}" ;then 140 | echo "alias sonarless='$HOME/.sonarless/makefile.sh'" >> "${sonarless_bashrc}" 141 | fi 142 | 143 | if [[ ! -s "${sonarless_zshrc}" ]] || ! grep -q 'sonarless' "${sonarless_zshrc}"; then 144 | echo "alias sonarless='$HOME/.sonarless/makefile.sh'" >> "${sonarless_zshrc}" 145 | fi 146 | 147 | # Dynamically create the alias during installation so that use can use it 148 | if ! command -v sonarless > /dev/null; then 149 | alias sonarless='$HOME/.sonarless/makefile.sh' 150 | fi 151 | 152 | echo "" 153 | echo "Please open a new terminal, or run the following in the existing one:" 154 | echo "" 155 | echo " alias sonarless='$HOME/.sonarless/makefile.sh' " 156 | echo "" 157 | echo "Then issue the following command:" 158 | echo "" 159 | echo " sonarless help" 160 | echo "" 161 | echo "Enjoy!!!" -------------------------------------------------------------------------------- /makefile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export SONAR_INSTANCE_NAME=${SONAR_INSTANCE_NAME:-"sonar-server"} 4 | export SONAR_INSTANCE_PORT=${SONAR_INSTANCE_PORT:-"9234"} 5 | export SONAR_PROJECT_NAME="${SONAR_PROJECT_NAME:-$(basename "$(pwd)")}" 6 | export SONAR_PROJECT_KEY="${SONAR_PROJECT_KEY:-$(basename "$(pwd)")}" 7 | export SONAR_GITROOT=${SONAR_GITROOT:-"$(pwd)"} 8 | export SONAR_SOURCE_PATH=${SONAR_SOURCE_PATH:-"."} 9 | export SONAR_METRICS_PATH=${SONAR_METRICS_PATH:-"./sonar-metrics.json"} 10 | export SONAR_EXTENSION_DIR="${HOME}/.sonarless/extensions" 11 | 12 | export DOCKER_SONAR_CLI=${DOCKER_SONAR_CLI:-"sonarsource/sonar-scanner-cli:11.3"} 13 | export DOCKER_SONAR_SERVER=${DOCKER_SONAR_SERVER:-"sonarqube:25.5.0.107428-community"} 14 | 15 | export CLI_NAME="sonarless" 16 | 17 | function uri_wait(){ 18 | set +e 19 | URL=$1 20 | SLEEP_INT=${2:-60} 21 | for _ in $(seq 1 "${SLEEP_INT}"); do 22 | sleep 1 23 | printf . 24 | HTTP_CODE=$(curl -k -s -o /dev/null -I -w "%{http_code}" -H 'User-Agent: Mozilla/6.0' "${URL}") 25 | [[ "${HTTP_CODE}" == "200" ]] && EXIT_CODE=0 || EXIT_CODE=-1 26 | [[ "${EXIT_CODE}" -eq 0 ]] && echo && return 27 | done 28 | echo 29 | set -e 30 | return "${EXIT_CODE}" 31 | } 32 | 33 | function help() { 34 | echo '' 35 | echo ' _ ' 36 | echo ' ___ ___ _ __ __ _ _ __ | | ___ ___ ___ ' 37 | echo ' / __| / _ \ | "_ \ / _` || "__|| | / _ \/ __|/ __| ' 38 | echo ' \__ \| (_) || | | || (_| || | | || __/\__ \\__ \ ' 39 | echo ' |___/ \___/ |_| |_| \__,_||_| |_| \___||___/|___/ ' 40 | echo '' 41 | echo '' 42 | echo "${CLI_NAME} help : this help menu" 43 | echo '' 44 | echo "${CLI_NAME} scan : to scan all code in current directory. Sonarqube Service will be started" 45 | echo "${CLI_NAME} results : show scan results and download the metric json (sonar-metrics.json) in current directory" 46 | echo '' 47 | echo "${CLI_NAME} start : start SonarQube Service docker instance with creds: admin/sonarless" 48 | echo "${CLI_NAME} stop : stop SonarQube Service docker instance" 49 | echo '' 50 | echo "${CLI_NAME} uninstall : uninstall all scriptlets and docker instances" 51 | echo "${CLI_NAME} docker-clean: remove all docker instances. Note any scan history will be lost as docker instance are deleted" 52 | echo '' 53 | } 54 | 55 | function start() { 56 | docker-deps-get 57 | sonar-ext-get 58 | 59 | if ! docker inspect "${SONAR_INSTANCE_NAME}" > /dev/null 2>&1; then 60 | docker run -d --name "${SONAR_INSTANCE_NAME}" -p "${SONAR_INSTANCE_PORT}:9000" --network "${CLI_NAME}" \ 61 | -v "${SONAR_EXTENSION_DIR}:/opt/sonarqube/extensions/plugins" \ 62 | -v "${SONAR_EXTENSION_DIR}:/usr/local/bin" \ 63 | "${DOCKER_SONAR_SERVER}" > /dev/null 2>&1 64 | else 65 | docker start "${SONAR_INSTANCE_NAME}" > /dev/null 2>&1 66 | fi 67 | 68 | # 1. Wait for services to be up 69 | printf "Booting SonarQube docker instance " 70 | uri_wait "http://localhost:${SONAR_INSTANCE_PORT}" 60 71 | printf 'Waiting for SonarQube service availability ' 72 | for _ in $(seq 1 180); do 73 | sleep 1 74 | printf . 75 | status_value=$(curl -s "http://localhost:${SONAR_INSTANCE_PORT}/api/system/status" | jq -r '.status') 76 | 77 | # Check if the status value is "running" 78 | if [[ "$status_value" == "UP" ]]; then 79 | echo 80 | break 81 | fi 82 | done 83 | 84 | status_value=$(curl -s "http://localhost:${SONAR_INSTANCE_PORT}/api/system/status" | jq -r '.status') 85 | # Check if the status value is "running" 86 | if [[ "$status_value" == "UP" ]]; then 87 | echo "SonarQube is running" 88 | else 89 | docker logs -f "${SONAR_INSTANCE_NAME}" 90 | echo "SonarQube is NOT running, exiting" 91 | exit 1 92 | fi 93 | 94 | # 2. Reset admin password to sonarless123 95 | curl -s -X POST -u "admin:admin" \ 96 | -d "login=admin&previousPassword=admin&password=Son@rless123" \ 97 | "http://localhost:${SONAR_INSTANCE_PORT}/api/users/change_password" 98 | echo "Local sonarqube URI: http://localhost:${SONAR_INSTANCE_PORT}" 99 | 100 | echo "Credentials: admin/Son@rless123" 101 | 102 | } 103 | 104 | function stop() { 105 | docker stop "${SONAR_INSTANCE_NAME}" > /dev/null 2>&1 && echo "Local SonarQube has been stopped" 106 | } 107 | 108 | function scan() { 109 | start 110 | 111 | # 1. Create default project and set default fav 112 | curl -s -u "admin:Son@rless123" -X POST "http://localhost:${SONAR_INSTANCE_PORT}/api/projects/create?name=${SONAR_PROJECT_NAME}&project=${SONAR_PROJECT_NAME}" | jq 113 | curl -s -u "admin:Son@rless123" -X POST "http://localhost:${SONAR_INSTANCE_PORT}/api/users/set_homepage?type=PROJECT&component=${SONAR_PROJECT_NAME}" 114 | 115 | echo "SONAR_GITROOT: ${SONAR_GITROOT}" 116 | echo "SONAR_SOURCE_PATH: ${SONAR_SOURCE_PATH}" 117 | 118 | # 2. Create token and scan using internal-ip becos of docker to docker communication 119 | SONAR_TOKEN=$(curl -s -X POST -u "admin:Son@rless123" "http://localhost:${SONAR_INSTANCE_PORT}/api/user_tokens/generate?name=$(date +%s%N)" | jq -r .token) 120 | export SONAR_TOKEN 121 | 122 | docker run --rm --network "${CLI_NAME}" \ 123 | -e SONAR_HOST_URL="http://${SONAR_INSTANCE_NAME}:9000" \ 124 | -e SONAR_TOKEN="${SONAR_TOKEN}" \ 125 | -e SONAR_SCANNER_OPTS="-Dsonar.projectKey=${SONAR_PROJECT_NAME} -Dsonar.sources=${SONAR_SOURCE_PATH}" \ 126 | -v "${SONAR_GITROOT}:/usr/src" \ 127 | "${DOCKER_SONAR_CLI}"; 128 | SCAN_RET_CODE="$?" 129 | 130 | # 3. Wait for scanning to be done 131 | if [[ "${SCAN_RET_CODE}" -eq "0" ]]; then 132 | printf '\nWaiting for analysis' 133 | for _ in $(seq 1 120); do 134 | sleep 1 135 | printf . 136 | status_value=$(curl -s -u "admin:Son@rless123" "http://localhost:${SONAR_INSTANCE_PORT}/api/qualitygates/project_status?projectKey=${SONAR_PROJECT_NAME}" | jq -r .projectStatus.status) 137 | # Checking if the status value is not "NONE" 138 | if [[ "$status_value" != "NONE" ]]; then 139 | echo 140 | echo "SonarQube scanning done" 141 | echo "Use webui http://localhost:${SONAR_INSTANCE_PORT} (admin/sonarless) or 'sonarless results' to get scan outputs" 142 | break 143 | fi 144 | done 145 | else 146 | printf '\nSonarQube scanning failed!' 147 | fi 148 | } 149 | 150 | function results() { 151 | # use this params to collect stats 152 | curl -s -u "admin:Son@rless123" "http://localhost:${SONAR_INSTANCE_PORT}/api/measures/component?component=${SONAR_PROJECT_NAME}&metricKeys=bugs,vulnerabilities,code_smells,quality_gate_details,violations,duplicated_lines_density,ncloc,coverage,reliability_rating,security_rating,security_review_rating,sqale_rating,security_hotspots,open_issues" \ 153 | | jq -r > "${SONAR_GITROOT}/${SONAR_METRICS_PATH}" 154 | cat "${SONAR_GITROOT}/${SONAR_METRICS_PATH}" 155 | echo "Scan results written to ${SONAR_GITROOT}/${SONAR_METRICS_PATH}" 156 | } 157 | 158 | function docker-deps-get() { 159 | ( docker image inspect "${DOCKER_SONAR_SERVER}" > /dev/null 2>&1 || echo "Downloading SonarQube..."; docker pull "${DOCKER_SONAR_SERVER}" > /dev/null 2>&1 ) & 160 | ( docker image inspect "${DOCKER_SONAR_CLI}" > /dev/null 2>&1 || echo "Downloading Sonar CLI..."; docker pull "${DOCKER_SONAR_CLI}" > /dev/null 2>&1 ) & 161 | wait 162 | docker network inspect "${CLI_NAME}" > /dev/null 2>&1 || docker network create "${CLI_NAME}" > /dev/null 2>&1 163 | } 164 | 165 | function sonar-ext-get() { 166 | 167 | [ ! -d "${SONAR_EXTENSION_DIR}" ] && echo "Downloading SonarQube Extensions..."; mkdir -p "${SONAR_EXTENSION_DIR}" 168 | 169 | if [ ! -f "${SONAR_EXTENSION_DIR}/shellcheck" ]; then 170 | # src: https://github.com/koalaman/shellcheck/blob/master/Dockerfile.multi-arch 171 | arch="$(uname -m)" 172 | os="$(uname | sed 's/.*/\L&/')" 173 | tag="v0.10.0" 174 | 175 | if [ "${arch}" = 'armv7l' ]; then 176 | arch='armv6hf' 177 | fi 178 | 179 | if [ "${arch}" = 'arm64' ]; then 180 | arch='aarch64' 181 | fi 182 | 183 | url_base='https://github.com/koalaman/shellcheck/releases/download/' 184 | tar_file="${tag}/shellcheck-${tag}.${os}.${arch}.tar.xz" 185 | curl -s --fail --location --progress-bar "${url_base}${tar_file}" | tar xJf - 186 | 187 | mv "shellcheck-${tag}/shellcheck" "${SONAR_EXTENSION_DIR}/" 188 | rm -rf "shellcheck-${tag}" 189 | fi 190 | 191 | SONAR_SHELLCHECK="sonar-shellcheck-plugin-2.5.0.jar" 192 | SONAR_SHELLCHECK_URL="https://github.com/sbaudoin/sonar-shellcheck/releases/download/v2.5.0/${SONAR_SHELLCHECK}" 193 | if [ ! -f "${SONAR_EXTENSION_DIR}/${SONAR_SHELLCHECK}" ]; then 194 | curl -s --fail --location --progress-bar "${SONAR_SHELLCHECK_URL}" > "${SONAR_EXTENSION_DIR}/${SONAR_SHELLCHECK}" 195 | fi 196 | 197 | } 198 | 199 | function docker-clean() { 200 | docker rm -f "${SONAR_INSTANCE_NAME}" 201 | docker image rm -f "${DOCKER_SONAR_CLI}" "${DOCKER_SONAR_SERVER}" 202 | docker volume prune -f 203 | docker network rm -f "${CLI_NAME}" 204 | } 205 | 206 | function uninstall() { 207 | docker-clean 208 | rm -rf "${HOME}/.${CLI_NAME}" 209 | } 210 | 211 | $* 212 | --------------------------------------------------------------------------------