├── .github └── workflows │ └── test_cra.yml ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── bito-action-script ├── README.md ├── bito-actions.sh └── bito_action.properties.sample ├── cra-scripts ├── bito-cra.properties ├── bito-cra.ps1 └── bito-cra.sh └── entrypoint.sh /.github/workflows/test_cra.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | types: [opened, synchronize] 4 | issue_comment: 5 | types: [created] 6 | 7 | jobs: 8 | code_review_job: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | contents: write 14 | name: Run code review agent on every pull request, respond to user comments 15 | 16 | steps: 17 | - name: Set Options Environment Variable 18 | run: | 19 | echo "EVENT_NAME=${{ github.event_name }}" >> $GITHUB_ENV 20 | GIT_DOMAIN_OPTION="" 21 | if [[ -n "${{ vars.GIT_DOMAIN }}" ]]; then 22 | GIT_DOMAIN_OPTION=" --git.domain=${{ vars.GIT_DOMAIN }}" 23 | fi 24 | STATIC_ANALYSIS_TOOL_OPTION="" 25 | if [[ -n "${{ vars.STATIC_ANALYSIS_TOOL }}" ]]; then 26 | STATIC_ANALYSIS_TOOL_OPTION=" --static_analysis_tool=${{ vars.STATIC_ANALYSIS_TOOL }}" 27 | fi 28 | REVIEW_SCOPE_OPTION="" 29 | if [[ -n "${{ vars.REVIEW_SCOPE }}" ]]; then 30 | REVIEW_SCOPE_OPTION=" --review_scope=${{ vars.REVIEW_SCOPE }}" 31 | fi 32 | EXCLUDE_BRANCHES_OPTION="" 33 | if [[ -n "${{ vars.EXCLUDE_BRANCHES }}" ]]; then 34 | EXCLUDE_BRANCHES_OPTION=" --exclude_branches=${{ vars.EXCLUDE_BRANCHES }}" 35 | fi 36 | EXCLUDE_FILES_OPTION="" 37 | if [[ -n "${{ vars.EXCLUDE_FILES }}" ]]; then 38 | EXCLUDE_FILES_OPTION=" --exclude_files=${{ vars.EXCLUDE_FILES }}" 39 | fi 40 | EXCLUDE_DRAFT_PR_OPTION="" 41 | if [[ -n "${{ vars.EXCLUDE_DRAFT_PR }}" ]]; then 42 | EXCLUDE_DRAFT_PR_OPTION=" --exclude_draft_pr=${{ vars.EXCLUDE_DRAFT_PR }}" 43 | fi 44 | echo "OPTIONS=--static_analysis.fb_infer.enabled=True --code_feedback=True --dependency_check.enabled=False --bee.path=/automation-platform --bee.actn_dir=/automation-platform/default_bito_ad/bito_modules --git.access_token=${{ secrets.GIT_ACCESS_TOKEN }} --bito_cli.bito.access_key=${{ secrets.BITO_ACCESS_KEY }}${GIT_DOMAIN_OPTION}${STATIC_ANALYSIS_TOOL_OPTION}${REVIEW_SCOPE_OPTION}${EXCLUDE_BRANCHES_OPTION}${EXCLUDE_FILES_OPTION}${EXCLUDE_DRAFT_PR_OPTION}" >> $GITHUB_ENV 45 | 46 | - name: Code Review Agent - Issue Comment 47 | if: github.event_name == 'issue_comment' 48 | uses: gitbito/codereviewagent@main 49 | with: 50 | pr: ${{ github.event.issue.pull_request.html_url }} 51 | command: ${{ github.event.comment.body }} 52 | options: ${{ env.OPTIONS }} 53 | 54 | - name: Code Review Agent action step 55 | if: github.event_name == 'pull_request' 56 | uses: gitbito/codereviewagent@main 57 | with: 58 | pr: ${{ github.event.pull_request.html_url }} 59 | command: review 60 | options: ${{ env.OPTIONS }} 61 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker:20.10 2 | 3 | RUN apk add bash 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | RUN chmod +x /entrypoint.sh 7 | 8 | 9 | ENTRYPOINT ["bash", "/entrypoint.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Bito 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | [![Visit bito.ai][bito-shield]][bito-url] 11 | [![Contributors][contributors-shield]][contributors-url] 12 | [![Forks][forks-shield]][forks-url] 13 | [![Stargazers][stars-shield]][stars-url] 14 | [![Issues][issues-shield]][issues-url] 15 | [![MIT License][license-shield]][license-url] 16 | 17 | 18 |
19 |
20 | 21 | Logo 22 | 23 | 24 |

AI Code Review Agent

25 | 26 |

27 | On-demand, context-aware code reviews in your Git workflow or IDE as you code. 28 |
29 | Explore the docs » 30 |
31 |
32 | View a demo 33 | · 34 | Signup for free 35 | · 36 | Join the community in Slack 37 |

38 |
39 | 40 |
41 | 42 | 43 |
44 | Table of contents 45 |
    46 |
  1. 47 | About the project 48 |
  2. 49 |
  3. 50 | Getting started 51 |
  4. 52 |
  5. 53 | Why use AI for code review? 54 |
  6. 55 |
  7. 56 | Key features 57 |
  8. 58 |
  9. 59 | Screenshots 60 |
  10. 61 |
  11. 62 | Need support? We're ready to assist! 63 |
  12. 64 | 65 |
66 |
67 | 68 |
69 | 70 | 71 | 72 | ## About the project 73 | 74 | > _Click the image below to watch the demo video on YouTube._ 75 | 76 | [![Bito's AI Code Review Agent](https://img.youtube.com/vi/ZrfSDANgboU/0.jpg)](https://www.youtube.com/watch?v=ZrfSDANgboU) 77 | 78 | Bito’s **[AI Code Review Agent](https://bito.ai/ai-code-review-agent/)** is the first agent built with **Bito’s AI Agent framework and engine**. It is an automated AI assistant (powered by Anthropic’s Claude Sonnet 3.5) that will review your team’s code; it spots bugs, issues, code smells, and security vulnerabilities in Pull/Merge Requests (PR/MR) and provides high-quality suggestions to fix them. 79 | 80 | It seamlessly **integrates with Git providers such as GitHub, GitLab, and Bitbucket**, automatically posting recommendations directly as comments within the corresponding Pull Request. It includes real-time recommendations from static analysis and OSS vulnerability tools such as fbinfer, Dependency-Check, etc., and can include high severity suggestions from other 3rd party tools you use such as Snyk or Sonar. 81 | 82 | The AI Code Review Agent is equipped with advanced code understanding capabilities, allowing it to analyze your entire codebase in depth. This results in more context-aware insights and suggestions, providing a tailored and highly relevant code review experience that aligns with the specific needs of your project. 83 | 84 | The AI Code Review Agent ensures a secure and confidential experience without compromising on reliability. Bito neither reads nor stores your code, and none of your code is used for AI model training. Learn more about our **[Privacy & Security practices](https://docs.bito.ai/privacy-and-security)**. 85 | 86 |
87 | 88 | 89 | 90 | ## Getting started 91 | 92 | There are three ways to use the AI Code Review Agent. 93 | 94 | **1- Bito Cloud:** Offers a hassle-free experience with no installation required on your machine. 95 | [Follow this guide](https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud) 96 | 97 | **2- Self-hosted service via CLI, webhooks, or GitHub Actions:** Ideal for deployments within your own infrastructure. 98 | [Follow this guide](https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-as-a-self-hosted-service) 99 | 100 | **3- AI code reviews in IDE:** Get instant feedback on your code changes directly within VS Code or JetBrains IDEs. 101 | [Follow this guide](https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/ai-code-reviews-in-ide) 102 | 103 |
104 | 105 | ## Why use AI for code review? 106 | 107 | - **Time saving:** Can reduce code review time by up to 50%. 108 | - **Quality improvement:** Enhances code review quality. 109 | - **Support role:** Assists senior software engineers, focusing on mundane review tasks. 110 | 111 |
112 | 113 | ## Key features 114 | 115 | - **AI code review:** AI analyzes your code changes to identify issues related to security, performance, scalability, optimization, impact on existing features, code structure, and coding standards. 116 | - **Deep code understanding:** Deep understanding of your code including libraries, frameworks, functionality to improve code review. 117 | - **Real-time feedback:** Get instant code review feedback in VS Code and all JetBrains IDEs. 118 | - **Pull request (PR) summary:** Quick overview of pull request. 119 | - **Feedback in pull requests**: Posts review comments directly in pull requests. 120 | - **Estimated effort to review:** Evaluates complexity for better planning. 121 | - **Tailored code suggestions:** Provides specific line-by-line code improvement suggestions. 122 | - **Static code analysis:** Uses tools like fbinfer, supports integration with tools like Sonar and more. 123 | - **Security vulnerability check:** Uses tools like OWASP Dependency-Check for detecting high-severity vulnerabilities in the open source projects you use. 124 | 125 |
126 | 127 | ## Screenshots 128 | 129 | 130 | ### Screenshot # 1 131 | 132 | > _AI-generated pull request (PR) summary_ 133 |
134 | 135 | 136 | AI-generated pull request (PR) summary 137 | 138 | 139 |
140 | 141 | --- 142 | 143 |
144 | 145 | ### Screenshot # 2 146 | 147 | > _Code review manually triggered using **/review** command._ 148 |
149 | 150 | 151 | Use the /review command to manually trigger a code review. 152 | 153 | 154 |
155 | 156 | --- 157 | 158 |
159 | 160 | ### Screenshot # 3 161 | 162 | > _Using tools like Facebook’s open source fbinfer (available out of the box), the Agent thoroughly analyzes your language-specific code and suggests fixes. Tools you use such as Sonar can also be configured._ 163 |
164 | 165 | 166 | Static Code Analysis reports inside AI code review 167 | 168 | 169 |
170 | 171 | --- 172 | 173 |
174 | 175 | ### Screenshot # 4 176 | 177 | > _The Agent checks your code in real-time for high-severity security vulnerabilities using OWASP Dependency-Check (available out of the box). Additional tools like Snyk or GitHub Dependabot can also be configured._ 178 |
179 | 180 | 181 | AI Code Review Agent checks your code in real-time for high-severity security vulnerabilities using OWASP Dependency-Check 182 | 183 | 184 |
185 | 186 | --- 187 | 188 |
189 | 190 | ### Screenshot # 5 191 | 192 | > _Get instant feedback on your code changes directly within VS Code or JetBrains IDEs._ 193 |
194 | 195 | 196 | Get instant feedback on your code changes directly within VS Code or JetBrains IDEs. 197 | 198 | 199 |
200 | 201 | --- 202 | 203 |
204 | 205 | ## Need support? We're ready to assist! 206 | 207 | For comprehensive information and guidance on the AI Code Review Agent, including installation and configuration instructions, please refer to our detailed **[documentation available here](https://docs.bito.ai/bito-dev-agents/ai-code-review-agent)**. Should you require further assistance or have any inquiries, our support team is readily available to assist you. 208 | 209 | Feel free to reach out to us via email at: **[support@bito.ai](mailto:support@bito.ai)** 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | [bito-shield]: https://img.shields.io/badge/Visit%20bito.ai-black.svg?style=for-the-badge&colorB=%232baaff 219 | [bito-url]: https://bito.ai/ 220 | 221 | [contributors-shield]: https://img.shields.io/github/contributors/gitbito/CodeReviewAgent.svg?style=for-the-badge 222 | [contributors-url]: https://github.com/gitbito/CodeReviewAgent/graphs/contributors 223 | [forks-shield]: https://img.shields.io/github/forks/gitbito/CodeReviewAgent.svg?style=for-the-badge 224 | [forks-url]: https://github.com/gitbito/CodeReviewAgent/network/members 225 | [stars-shield]: https://img.shields.io/github/stars/gitbito/CodeReviewAgent.svg?style=for-the-badge 226 | [stars-url]: https://github.com/gitbito/CodeReviewAgent/stargazers 227 | [issues-shield]: https://img.shields.io/github/issues/gitbito/CodeReviewAgent.svg?style=for-the-badge 228 | [issues-url]: https://github.com/gitbito/CodeReviewAgent/issues 229 | [license-shield]: https://img.shields.io/github/license/gitbito/CodeReviewAgent.svg?style=for-the-badge 230 | [license-url]: https://github.com/gitbito/CodeReviewAgent?tab=MIT-1-ov-file#readme 231 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | # action.yml 2 | name: 'Docker Run Action' 3 | description: 'Run a command in a new container' 4 | inputs: 5 | options: 6 | description: 'additional parameters to run the Code Review Agent' 7 | required: true 8 | pr: 9 | description: 'PR URL which needs to be reviewed' 10 | required: true 11 | command: 12 | description: 'Use a specific command' 13 | required: true 14 | default: review 15 | runs: 16 | using: 'docker' 17 | image: 'Dockerfile' -------------------------------------------------------------------------------- /bito-action-script/README.md: -------------------------------------------------------------------------------- 1 | # Bito Action 2 | 3 | This document provides a step-by-step guide for setting up and running the Bito Action Script. The Bito Action Script allows you to configure and run automated code reviews using the Bito Code Review Agent (CRA). It enables you to seamlessly integrate the CRA into your Continuous Integration/Continuous Deployment (CI/CD) pipeline. Follow the instructions below to configure and execute the script successfully. 4 | 5 | ## Steps for Setup 6 | 7 | ### 1. Login to Bito 8 | 9 | - Navigate to the Bito platform: [Bito Login](https://alpha.bito.ai/auth/login). 10 | - Use your credentials to log in to your account. 11 | 12 | ### 2. Create a New Agent Configuration Instance 13 | 14 | - After logging in, go to **Configured Agents**. 15 | - Select **Code Review Agent (CRA)**. 16 | - Click on **Create New Instance**. 17 | 18 | ### 3. Configure the CRA Agent 19 | 20 | - **Git Access Token:** During the setup process, you'll need to provide a Git access token to allow the CRA to access your repositories. Please refer to [Bito Documentation](https://docs.bito.ai/) to create a Git access token. 21 | 22 | - **Agent Credentials:** After a successful configuration, you'll receive a unique **Agent Instance URL** and **Agent Instance Secret**. These credentials are essential for configuring the Bito Action Script. 23 | 24 | - **Webhook Configuration:** After configuring the CRA, set up the webhook in your selected Git provider. This webhook is necessary to gather analytical data, such as acceptance rates and feedback. Please refer to [Webhook Configuration Documentation](https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-as-a-self-hosted-service/install-run-via-webhooks-service#webhook-setup-guide). 25 | 26 | ### 4. Download Bito Action Script 27 | 28 | - Download the Bito Action Script and a sample configuration file from the following repository: [Bito Action Script on GitHub](https://github.com/gitbito/CodeReviewAgent/tree/main/bito-action-script). 29 | 30 | ### 5. Update the Property File 31 | 32 | - Open the `bito_action.properties` file located in the downloaded script folder. 33 | - Update the following properties with the information provided during the CRA configuration: 34 | 35 | - agent_instance_url= 36 | - agent_instance_secret= 37 | - pr_url= (Optional if using the runtime URL method) 38 | 39 | ### 6. Run the Bito Action Script 40 | 41 | You can run the Bito Action Script in two different ways, depending on your preference: 42 | 43 | #### Option 1: Using the Property File and Runtime Git URL 44 | 45 | - Ensure the `bito_action.properties` file is updated with the correct values. 46 | - Run the following command: 47 | 48 | ```bash 49 | bash ./bito_actions.sh bito_action.properties pr_url= 50 | ``` 51 | - Replace  with the pull request URL you want to review. 52 | 53 | #### Option 2: Using Runtime Values 54 | 55 | - Provide all necessary values directly in the command line: 56 | 57 | ```bash 58 | bash ./bito_actions.sh agent_instance_url= agent_instance_secret= pr_url= 59 | ``` 60 | - Replace , and  with your specific values. 61 | 62 | ### 7. Integrate Bito Action Script into CI/CD Pipeline 63 | 64 | - Incorporate the Bito Action Script into your CI/CD pipeline by including the appropriate commands in your build or deployment scripts. 65 | 66 | - This integration ensures that code reviews are automatically triggered as part of the pipeline, enhancing your development workflow by enforcing code quality checks on every code change. 67 | 68 | ## Script Responses 69 | 70 | During execution, the script will return various responses based on the success or failure of the process. Below are the possible responses: 71 | 72 | ### 1. Success 73 | **Response:** 74 | ```plaintext 75 | Success:- Job Started with Id : ce82fae8-05da-4389-bddc-86ed583ab053 76 | 77 | ``` 78 | ### 2. Invalid Secret 79 | **Response:** 80 | ```plaintext 81 | {"status":1,"response":"Secret is not valid","created":"2024-08-09T12:32:23.060340616Z"} 82 | 83 | ``` 84 | ### 3. Invalid Instance URL 85 | **Response:** 86 | ```plaintext 87 | {"status":1,"response":"webhook is invalid: Please create a new instance","created":"2024-08-09T12:33:07.050869506Z"} 88 | 89 | ``` 90 | ### 4. Missing Input Data for Script 91 | **Response:** 92 | ```plaintext 93 | Error: pr_url is empty 94 | 95 | ``` 96 | 97 | ## Example Property File 98 | 99 | Below is a sample `bito_action.properties` file: 100 | ```plaintext 101 | agent_instance_url=your_agent_instance_url 102 | agent_instance_secret=your_agent_secret 103 | pr_url= 104 | 105 | ``` 106 | 107 | ## Conclusion 108 | 109 | You are now ready to use the Bito Action Script for automated code reviews through CI/CD Pipelines. Ensure all configurations are correct before running the script. If you encounter any issues, consult the [Bito Documentation](https://docs.bito.ai/) or reach out to Bito support for assistance. 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /bito-action-script/bito-actions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Function to read property values from the file 4 | read_property() { 5 | local property_key=$1 6 | local property_file=$2 7 | local property_value=$(grep -w "${property_key}" "${property_file}" | cut -d'=' -f2-) 8 | echo "${property_value//\"}" 9 | } 10 | 11 | # Initialize variables with default empty values 12 | agent_instance_url="" 13 | agent_instance_secret="" 14 | pr_url="" 15 | 16 | # Check if the first argument is a file 17 | if [ -f "$1" ]; then 18 | PROPERTY_FILE=$1 19 | shift 20 | 21 | # Read initial values from the property file 22 | agent_instance_url=$(read_property "agent_instance_url" "${PROPERTY_FILE}") 23 | agent_instance_secret=$(read_property "agent_instance_secret" "${PROPERTY_FILE}") 24 | pr_url=$(read_property "pr_url" "${PROPERTY_FILE}") 25 | fi 26 | 27 | # Override with command line arguments if provided 28 | for arg in "$@" 29 | do 30 | case $arg in 31 | agent_instance_url=*) 32 | agent_instance_url="${arg#*=}" 33 | agent_instance_url="${agent_instance_url//\"}" 34 | ;; 35 | agent_instance_secret=*) 36 | agent_instance_secret="${arg#*=}" 37 | agent_instance_secret="${agent_instance_secret//\"}" 38 | ;; 39 | pr_url=*) 40 | pr_url="${arg#*=}" 41 | pr_url="${pr_url//\"}" 42 | ;; 43 | *) 44 | echo "Unknown argument: $arg" 45 | ;; 46 | esac 47 | done 48 | 49 | # Check if any of the required properties are empty 50 | if [ -z "$agent_instance_url" ]; then 51 | echo "Error: agent_instance_url is empty" 52 | exit 1 53 | fi 54 | 55 | if [ -z "$agent_instance_secret" ]; then 56 | echo "Error: agent_instance_secret is empty" 57 | exit 1 58 | fi 59 | 60 | if [ -z "$pr_url" ]; then 61 | echo "Error: pr_url is empty" 62 | exit 1 63 | fi 64 | 65 | # Print properties 66 | echo "Agent Instance URL: $agent_instance_url" 67 | echo "Git URL: $pr_url" 68 | 69 | # Execute the curl command 70 | eval "curl --location '$agent_instance_url' \ 71 | --header 'X-Bito-Action-Token: $agent_instance_secret' \ 72 | --header 'Content-Type: application/json' \ 73 | --data '{ 74 | \"git_url\": \"$pr_url\", 75 | \"command\": \"review\", 76 | \"arguments\": {} 77 | }'" 78 | -------------------------------------------------------------------------------- /bito-action-script/bito_action.properties.sample: -------------------------------------------------------------------------------- 1 | agent_instance_url= 2 | agent_instance_secret= 3 | pr_url= 4 | -------------------------------------------------------------------------------- /cra-scripts/bito-cra.properties: -------------------------------------------------------------------------------- 1 | mode=server 2 | pr_url= 3 | code_feedback=True 4 | bito_cli.bito.access_key= 5 | git.provider= 6 | git.access_token= 7 | git.domain= 8 | static_analysis=True 9 | static_analysis_tool=fb_infer,astral_ruff,mypy 10 | linters_feedback=True 11 | secret_scanner_feedback=True 12 | review_scope= 13 | dependency_check=False 14 | code_context=True 15 | dependency_check.snyk_auth_token= 16 | review_comments=2 17 | server_port=10051 18 | cra_version=latest 19 | include_source_branches= 20 | include_target_branches= 21 | exclude_files=*.xml,*.json,*.properties,.gitignore,*.yml,*.md 22 | exclude_draft_pr=True 23 | post_as_request_changes=False 24 | support_email= 25 | suggestion_mode=essential 26 | -------------------------------------------------------------------------------- /cra-scripts/bito-cra.ps1: -------------------------------------------------------------------------------- 1 | # Variables for temp files. 2 | $BITOAIDIR = Join-Path $HOME ".bitoai" 3 | if (-not (Test-Path $BITOAIDIR)) { 4 | New-Item -ItemType Directory -Path $BITOAIDIR 5 | } 6 | $BITOCRALOCKFILE = Join-Path $BITOAIDIR "bitocra.lock" 7 | $BITOCRACID = Join-Path $BITOAIDIR "bitocra.cid" 8 | 9 | # Function to validate Docker version 10 | function Validate-DockerVersion { 11 | # Get the Docker version 12 | $dockerVersion = docker version --format '{{.Server.Version}}' 13 | # Extract the major version number 14 | $majorVersion = ($dockerVersion -split '\.')[0] 15 | # Check if the Docker version is less than 20.x 16 | if ($majorVersion -lt 20) { 17 | Write-Host "Docker version $dockerVersion is not supported. Please upgrade to Docker 20.x or higher." 18 | exit 1 19 | } 20 | } 21 | 22 | # Function to validate PowerShell version 23 | function Validate-PowerShellVersion { 24 | # Get the PowerShell version 25 | $psVersion = $PSVersionTable.PSVersion 26 | # Extract the major version number 27 | $majorVersion = $psVersion.Major 28 | # Check if the PowerShell version is less than 4.x 29 | if ($majorVersion -lt 5) { 30 | Write-Host "PowerShell version $($psVersion.ToString()) is not supported. Please upgrade to PowerShell 5.x or higher." 31 | exit 1 32 | } 33 | } 34 | 35 | # Function to validate a URL (basic validation) 36 | function Validate-Url { 37 | param($url) 38 | if (-not($url -match "^https?://")) { 39 | Write-Host "Invalid URL. Please enter a valid URL." 40 | exit 1 41 | } 42 | } 43 | 44 | # Function to validate a git provider value i.e. either GITLAB or GITHUB 45 | function Validate-GitProvider { 46 | param($git_provider_val) 47 | 48 | # Convert the input to uppercase 49 | $git_provider_val = $git_provider_val.ToUpper() 50 | 51 | # Check if the converted value is either "GITLAB" or "GITHUB" or "BITBUCKET" 52 | if ($git_provider_val -ne "GITLAB" -and $git_provider_val -ne "GITHUB" -and $git_provider_val -ne "BITBUCKET") { 53 | Write-Host "Invalid git provider value. Please enter either GITLAB or GITHUB or BITBUCKET." 54 | exit 1 55 | } 56 | 57 | # Return the properly cased value 58 | return $git_provider_val 59 | } 60 | 61 | # Function to validate a boolean value i.e. string compare against "True" or "False" 62 | function Validate-Boolean { 63 | param($boolean_val) 64 | # Convert the input to title case (first letter uppercase, rest lowercase) 65 | $boolean_val = $boolean_val.Substring(0,1).ToUpper() + $boolean_val.Substring(1).ToLower() 66 | 67 | # Check if the converted value is either "True" or "False" 68 | if ($boolean_val -ne "True" -and $boolean_val -ne "False") { 69 | Write-Host "Invalid boolean value. Please enter either True or False." 70 | exit 1 71 | } 72 | 73 | # Return the properly cased boolean value 74 | return $boolean_val 75 | } 76 | 77 | # Function to set default suggestion mode 78 | function Validate-Suggestion-Mode { 79 | param($suggestion_mode) 80 | # Convert the input to lowercase 81 | $suggestion_mode = $suggestion_mode.ToLower() 82 | 83 | if ($suggestion_mode -eq "comprehensive") { 84 | return $suggestion_mode 85 | } 86 | 87 | return "essential" 88 | } 89 | 90 | # Function to validate a mode value i.e. cli or server 91 | function Validate-Mode { 92 | param($mode_val) 93 | if ($mode_val -ne "cli" -and $mode_val -ne "server") { 94 | Write-Host "Invalid mode value. Please enter either cli or server." 95 | exit 1 96 | } 97 | } 98 | 99 | # Function to validate an environment value i.e. prod or staging 100 | function Validate-Env { 101 | param($env_val) 102 | 103 | if ($env_val -ne "prod" -and $env_val -ne "staging" -and $env_val -ne "preprod") { 104 | Write-Host "Invalid env value. Please enter either prod or staging or preprod." 105 | exit 1 106 | } 107 | } 108 | 109 | # Function to validate a review_comments vallue i.e. 1 mapped to "FULLPOST" or 2 mapped to "INLINE" 110 | function Validate-ReviewComments { 111 | param($reviewcomments_val) 112 | 113 | # Check if the provided value is either "1" or "2" 114 | if ($reviewcomments_val -ne "1" -and $reviewcomments_val -ne "2") { 115 | Write-Host "Invalid review comments value. Please enter either 1 or 2." 116 | exit 1 117 | } 118 | 119 | if ($reviewcomments_val -eq "1") { 120 | return "FULLPOST" 121 | } 122 | 123 | if ($reviewcomments_val -eq "2") { 124 | return "INLINE" 125 | } 126 | } 127 | 128 | $crEventType = "automated" 129 | function ValidateCrEventType { 130 | param($crEventTypeVal) 131 | if ($crEventTypeVal -eq "manual"){ 132 | return "manual" 133 | }else { 134 | return "automated" 135 | } 136 | } 137 | 138 | $postingToPr = "True" 139 | function ValidatePostingToPr { 140 | param($boolean_val) 141 | # Convert the input to title case (first letter uppercase, rest lowercase) 142 | $boolean_val = $boolean_val.Substring(0,1).ToUpper() + $boolean_val.Substring(1).ToLower() 143 | 144 | # Check if the converted value is either "True" or "False" 145 | if ($boolean_val -ne "True" -and $boolean_val -ne "False") { 146 | return $postingToPr 147 | } 148 | 149 | # Return the properly cased boolean value 150 | return $boolean_val 151 | 152 | } 153 | 154 | # Function to display URL using IP address and port 155 | # Run docker ps -l command and store the output 156 | function Display-DockerUrl { 157 | 158 | # Run docker ps -l command and store the output 159 | $containerInfo = docker ps -l | Select-Object -Skip 1 160 | 161 | # Extract IP address and port number using regex 162 | $ipAddress = $containerInfo -replace '.*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)->\d+/\w+.*', '$1' 163 | # Set IP address to 127.0.0.1 if it's 0.0.0.0 164 | if ($ipAddress -eq "0.0.0.0") { 165 | $ipAddress = "127.0.0.1" 166 | } 167 | $portNumber = $containerInfo -replace '.*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:(\d+)->\d+/\w+.*', '$1' 168 | 169 | # Print the IP address and port number 170 | #Write-Host "IP Address: $ipAddress" 171 | #Write-Host "Port Number: $portNumber" 172 | 173 | if ($ipAddress -ne '' -and $portNumber -ne '') { 174 | $url = "http://${ipAddress}:${portNumber}/" 175 | Write-Host "" 176 | Write-Host "Code Review Agent URL: $url" 177 | Write-Host "Note: Use the above URL to configure GITLAB/GITHUB webhook by replacing the IP address with the IP address or Domain Name of your server." 178 | } 179 | } 180 | 181 | function Display-Usage { 182 | Write-Host "Invalid command to execute Code Review Agent:" 183 | Write-Host "" 184 | Write-Host "Usage-1: $PSCommandPrefix " 185 | Write-Host "Usage-2: $PSCommandPrefix service start | restart " 186 | Write-Host "Usage-3: $PSCommandPrefix service stop" 187 | Write-Host "Usage-4: $PSCommandPrefix service status" 188 | Write-Host "Usage-5: $PSCommandPrefix pr_url=" 189 | } 190 | 191 | function Check-PropertyFile { 192 | param($prop_file) 193 | if (-not $prop_file) { 194 | Write-Host "Properties file not provided!" 195 | exit 1 196 | } 197 | if (-not(Test-Path $prop_file)) { 198 | Write-Host "Properties file not found!" 199 | exit 1 200 | } 201 | 202 | #return valid properties file 203 | return $prop_file 204 | } 205 | 206 | function Check-ActionDirectory { 207 | param($action_dir) 208 | if (-not $action_dir) { 209 | Write-Host "Action directory not provided!" 210 | exit 1 211 | } 212 | if (-not(Test-Path $action_dir -PathType Container)) { 213 | Write-Host "Action directory not found!" 214 | exit 1 215 | } 216 | 217 | #return valid action directory 218 | return $action_dir 219 | } 220 | 221 | # Function to check if the CLI directory exists 222 | function Check-CliDirectory { 223 | param($cli_dir) 224 | 225 | if (-not $cli_dir) { 226 | Write-Host "CLI directory not provided!" 227 | exit 1 228 | } 229 | 230 | if (-not (Test-Path -Path $cli_dir -PathType Container)) { 231 | Write-Host "CLI directory not found!" 232 | exit 1 233 | } 234 | 235 | #return valid cli directory 236 | return $cli_dir 237 | } 238 | 239 | # Function to check if the output path directory exists 240 | function Check-OutputDirectory { 241 | param($output_path) 242 | 243 | if (-not (Test-Path -Path $output_path -PathType Container)) { 244 | Write-Host "Output path directory not found!" 245 | exit 1 246 | } 247 | 248 | Write-Host "Output path: $output_path" 249 | #return valid cli directory 250 | return $output_path 251 | } 252 | 253 | function Stop-CRA { 254 | if (Test-Path "$BITOCRALOCKFILE") { 255 | Write-Host "Stopping the CRA..." 256 | $fileContent = Get-Content -Path "$BITOCRALOCKFILE" 257 | $containerIdLine = $fileContent | Where-Object { $_ -like 'export CONTAINER_ID=*' } 258 | $containerId = $containerIdLine -replace 'export CONTAINER_ID=', '' 259 | docker stop $containerId 260 | $RET_VAL = $LASTEXITCODE 261 | if ($RET_VAL -ne 0) { 262 | Write-Host "Could not stop CRA" 263 | exit 1 264 | } 265 | Remove-Item -Path "$BITOCRALOCKFILE" -Force 266 | } 267 | else { 268 | Write-Host "CRA is not running." 269 | } 270 | } 271 | 272 | function Check-CRA { 273 | if (Test-Path "$BITOCRALOCKFILE") { 274 | Write-Host "CRA is running." 275 | } 276 | else { 277 | Write-Host "CRA is not running." 278 | } 279 | } 280 | 281 | # Check if a properties file is provided as an argument 282 | if ($args.Count -lt 1) { 283 | $PSCommandPrefix = $MyInvocation.InvocationName 284 | Display-Usage 285 | exit 1 286 | } 287 | 288 | $properties_file = $null 289 | $action_directory = $null 290 | $force_mode = $null 291 | $pr_url_arg = $null 292 | 293 | function Process-PrUrlOrActionDirParam { 294 | param ($func_local_arg) 295 | 296 | if ($func_local_arg -like "pr_url=*") { 297 | $pr_url_arg = $arg -replace "pr_url=", "" 298 | } else { 299 | $action_directory = Check-ActionDirectory $func_local_arg 300 | } 301 | } 302 | 303 | if ($args.Count -gt 1) { 304 | if ($args[0] -eq "service") { 305 | switch ($args[1]) { 306 | "start" { 307 | $force_mode = "server" 308 | $properties_file = Check-PropertyFile $args[2] 309 | 310 | if (Test-Path "$BITOCRALOCKFILE") { 311 | Write-Host "CRA is already running." 312 | exit 0 313 | } 314 | 315 | Write-Host "Starting the CRA..." 316 | # Note down the hidden parameter for action directory 317 | if ($args.Count -eq 4) { 318 | $action_directory = Check-ActionDirectory $args[3] 319 | # Write-Host "Action Directory: $action_directory" 320 | } 321 | } 322 | "stop" { 323 | Stop-CRA 324 | exit 0 325 | } 326 | "restart" { 327 | $force_mode = "server" 328 | $properties_file = Check-PropertyFile $args[2] 329 | 330 | Stop-CRA 331 | Write-Host "Starting the CRA..." 332 | 333 | # Note down the hidden parameter for action directory 334 | if ($args.Count -eq 4) { 335 | $action_directory = Check-ActionDirectory $args[3] 336 | # Write-Host "Action Directory: $action_directory" 337 | } 338 | } 339 | "status" { 340 | Write-Host "Checking the CRA..." 341 | Check-CRA 342 | exit 0 343 | } 344 | default { 345 | $PSCommandPrefix = $MyInvocation.InvocationName 346 | Display-Usage 347 | exit 1 348 | } 349 | } 350 | } 351 | else { 352 | # Load properties from file 353 | $properties_file = Check-PropertyFile $args[0] 354 | 355 | # Note down the hidden parameter for action directory 356 | if ($args.Count -eq 2) { 357 | #check if 2nd argument is like pr_url= then extract value else check the action_directory 358 | Process-PrUrlOrActionDirParam $args[1] 359 | } 360 | 361 | if ($args.Count -eq 3) { 362 | #check if 2nd argument is like pr_url= then extract value else check the action_directory 363 | Process-PrUrlOrActionDirParam $args[1] 364 | #check if 3rd argument is like pr_url= then extract value else check the action_directory 365 | Process-PrUrlOrActionDirParam $args[2] 366 | } 367 | } 368 | } 369 | else { 370 | # Load properties from file 371 | $properties_file = Check-PropertyFile $args[0] 372 | } 373 | 374 | #validate the PowerShell version and docker version 375 | Validate-PowerShellVersion 376 | Validate-DockerVersion 377 | 378 | # Read properties into a hashtable 379 | $props = @{} 380 | Get-Content $properties_file | ForEach-Object { 381 | $line = $_ 382 | if (-not ($line -match '^#')) { 383 | $key, $value = $line -split '=', 2 384 | $props[$key.Trim()] = $value.Trim() 385 | } 386 | } 387 | 388 | # Override pr_url if provided as an argument 389 | if ($pr_url_arg) { 390 | $props["pr_url"] = $pr_url_arg 391 | } 392 | 393 | # Function to ask for missing parameters 394 | function Ask-For-Param { 395 | param($param_name, $exit_on_empty) 396 | $param_value = $props[$param_name] 397 | 398 | if ([string]::IsNullOrEmpty($param_value)) { 399 | $param_value = Read-Host "Enter value for $param_name" 400 | if ([string]::IsNullOrEmpty($param_value) -and $exit_on_empty) { 401 | Write-Host "No input provided for $param_name. Exiting." 402 | exit 1 403 | } else { 404 | $props[$param_name] = $param_value 405 | } 406 | } 407 | } 408 | 409 | # Parameters that are required/optional in mode cli 410 | $required_params_cli = @( 411 | "mode", 412 | "pr_url", 413 | "git.provider", 414 | "git.access_token", 415 | "bito_cli.bito.access_key", 416 | "code_feedback" 417 | ) 418 | 419 | $optional_params_cli = @( 420 | "acceptable_suggestions_enabled", 421 | "review_comments", 422 | "static_analysis", 423 | "static_analysis_tool", 424 | "linters_feedback", 425 | "secret_scanner_feedback", 426 | "review_scope", 427 | "enable_default_branch", 428 | "exclude_branches", 429 | "include_source_branches", 430 | "include_target_branches" 431 | "post_as_request_changes", 432 | "suggestion_mode", 433 | "locale", 434 | "exclude_files", 435 | "exclude_draft_pr", 436 | "dependency_check", 437 | "dependency_check.snyk_auth_token", 438 | "cra_version", 439 | "env", 440 | "cli_path", 441 | "output_path" 442 | "git.domain" 443 | "code_context" 444 | "cr_event_type" 445 | "posting_to_pr" 446 | "custom_rules.configured_ws_ids" 447 | "custom_rules.aws_access_key_id" 448 | "custom_rules.aws_secret_access_key" 449 | "custom_rules.region_name" 450 | "custom_rules.bucket_name" 451 | "custom_rules.aes_key" 452 | "support_email" 453 | ) 454 | 455 | # Parameters that are required/optional in mode server 456 | $required_params_server = @( 457 | "mode", 458 | "code_feedback" 459 | ) 460 | 461 | $optional_params_server = @( 462 | "acceptable_suggestions_enabled", 463 | "git.provider", 464 | "git.access_token", 465 | "bito_cli.bito.access_key", 466 | "review_comments", 467 | "static_analysis", 468 | "static_analysis_tool", 469 | "linters_feedback", 470 | "secret_scanner_feedback", 471 | "review_scope", 472 | "enable_default_branch", 473 | "exclude_branches", 474 | "include_source_branches", 475 | "include_target_branches", 476 | "post_as_request_changes", 477 | "suggestion_mode", 478 | "locale", 479 | "exclude_files", 480 | "exclude_draft_pr", 481 | "dependency_check", 482 | "dependency_check.snyk_auth_token", 483 | "server_port", 484 | "cra_version" 485 | "env" 486 | "cli_path" 487 | "git.domain" 488 | "code_context" 489 | "cr_event_type" 490 | "custom_rules.configured_ws_ids" 491 | "custom_rules.aws_access_key_id" 492 | "custom_rules.aws_secret_access_key" 493 | "custom_rules.region_name" 494 | "custom_rules.bucket_name" 495 | "custom_rules.aes_key" 496 | "output_path" 497 | "support_email" 498 | ) 499 | 500 | $bee_params = @( 501 | "bee.path", 502 | "bee.actn_dir" 503 | ) 504 | 505 | $props["bee.path"] = "/automation-platform" 506 | if ([string]::IsNullOrEmpty($action_directory)) { 507 | $props["bee.actn_dir"] = "/automation-platform/default_bito_ad/bito_modules" 508 | } else { 509 | $props["bee.actn_dir"] = "/action_dir" 510 | } 511 | 512 | # CRA Version 513 | $cra_version = "latest" 514 | $param_cra_version = "cra_version" 515 | if ($props[$param_cra_version] -ne '') { 516 | $cra_version = $props[$param_cra_version] 517 | } 518 | 519 | # Docker pull command 520 | $docker_pull = "docker pull bitoai/cra:${cra_version}" 521 | 522 | # Construct the docker run command 523 | $docker_init_cmd = "docker run --rm -it" 524 | if (-not([string]::IsNullOrEmpty($action_directory))) { 525 | $docker_init_cmd = "docker run --rm -it -v ${action_directory}:/action_dir" 526 | } 527 | 528 | $required_params = $required_params_cli 529 | $optional_params = $optional_params_cli 530 | $mode = "cli" 531 | $param_mode = "mode" 532 | $server_port = "10051" 533 | $param_server_port = "server_port" 534 | $command = "review" 535 | $docker_cmd = "" 536 | # handle if CRA is starting in server mode using start command. 537 | if ($force_mode) { 538 | $props[$param_mode] = $force_mode 539 | } 540 | Validate-Mode $props[$param_mode] 541 | if ($props[$param_mode] -eq "server") { 542 | $mode = "server" 543 | if ($props[$param_server_port] -ne '') { 544 | $server_port = $props[$param_server_port] 545 | } 546 | $required_params = $required_params_server 547 | $optional_params = $optional_params_server 548 | # Append -p and -d parameter in docker command 549 | $docker_cmd += " -p ${server_port}:${server_port} -d" 550 | } 551 | Write-Host "Bito Code Review Agent is running as: $mode" 552 | Write-Host "" 553 | 554 | # Append Docker Image and Tag Placeholder 555 | $docker_cmd += " bitoai/cra:${cra_version}" 556 | 557 | # Ask for required parameters if they are not set 558 | foreach ($param in $required_params) { 559 | Ask-For-Param $param $true 560 | } 561 | 562 | # Ask for optional parameters if they are not set 563 | foreach ($param in $optional_params) { 564 | if ($param -eq "dependency_check.snyk_auth_token" -and $props["dependency_check"] -eq "True") { 565 | Ask-For-Param $param $false 566 | } elseif ($param -ne "acceptable_suggestions_enabled" -and $param -ne "dependency_check.snyk_auth_token" -and $param -ne "env" -and $param -ne "cli_path" -and $param -ne "output_path" -and $param -ne "static_analysis_tool" -and $param -ne "linters_feedback" -and $param -ne "secret_scanner_feedback" -and $param -ne "enable_default_branch" -and $param -ne "git.domain" -and $param -ne "review_scope" -and $param -ne "exclude_branches" -and $param -ne "include_source_branches" -and $param -ne "include_target_branches" -and $param -ne "suggestion_mode" -and $param -ne "locale" -and $param -ne "exclude_files" -and $param -ne "exclude_draft_pr" -and $param -ne "cr_event_type" -and $param -ne "posting_to_pr" -and $param -ne "custom_rules.configured_ws_ids" -and $param -ne "custom_rules.aws_access_key_id" -and $param -ne "custom_rules.aws_secret_access_key" -and $param -ne "custom_rules.region_name" -and $param -ne "custom_rules.bucket_name" -and $param -ne "custom_rules.aes_key" -and $param -ne "code_context_config.partial_timeout" -and $param -ne "code_context_config.max_depth" -and $param -ne "code_context_config.kill_timeout_sec" -and $param -ne "support_email" -and $param -ne "post_as_request_changes") { 567 | Ask-For-Param $param $false 568 | } 569 | } 570 | 571 | # Append parameters to the docker command 572 | foreach ($param in $required_params + $bee_params + $optional_params) { 573 | if (-not([string]::IsNullOrEmpty($props[$param]))) { 574 | if ($param -eq "cra_version") { 575 | $cra_version = $props[$param] 576 | } elseif ($param -eq "server_port") { 577 | #assign docker port 578 | $server_port = $props[$param] 579 | $docker_cmd += " --$param=$($props[$param])" 580 | } elseif ($param -eq "pr_url") { 581 | $trimmedUrl = $props[$param].Trim() 582 | Validate-Url $trimmedUrl 583 | $docker_cmd += " --$param=$($trimmedUrl) --command=$($command) rest" 584 | } elseif ($param -eq "git.provider") { 585 | $validated_gitprovider = Validate-GitProvider $props[$param] 586 | $docker_cmd += " --$param=$validated_gitprovider" 587 | } elseif ($param -eq "static_analysis") { 588 | $validated_boolean = Validate-Boolean $props[$param] 589 | $docker_cmd += " --static_analysis.fb_infer.enabled=$validated_boolean" 590 | } elseif ($param -eq "static_analysis_tool") { 591 | $docker_cmd += " --$param=$($props[$param])" 592 | } elseif ($param -eq "linters_feedback") { 593 | $validated_boolean = Validate-Boolean $props[$param] 594 | $docker_cmd += " --$param=$validated_boolean" 595 | } elseif ($param -eq "post_as_request_changes") { 596 | $validated_boolean = Validate-Boolean $props[$param] 597 | $docker_cmd += " --$param=$validated_boolean" 598 | } elseif ($param -eq "secret_scanner_feedback") { 599 | $validated_boolean = Validate-Boolean $props[$param] 600 | $docker_cmd += " --$param=$validated_boolean" 601 | } elseif ($param -eq "acceptable_suggestions_enabled") { 602 | $validated_boolean = Validate-Boolean $props[$param] 603 | $docker_cmd += " --$param=$validated_boolean" 604 | } elseif ($param -eq "review_scope") { 605 | $scopes = $($props[$param]) -replace ',\s*', ',' 606 | $docker_cmd += " --$param='[$scopes]'" 607 | } elseif ($param -eq "enable_default_branch") { 608 | $validated_boolean = Validate-Boolean $props[$param] 609 | $docker_cmd += " --$param=$validated_boolean" 610 | } elseif ($param -eq "exclude_branches") { 611 | $docker_cmd += " --exclude_branches='$($props[$param])'" 612 | } elseif ($param -eq "include_source_branches") { 613 | $docker_cmd += " --include_source_branches='$($props[$param])'" 614 | } elseif ($param -eq "include_target_branches") { 615 | $docker_cmd += " --include_target_branches='$($props[$param])'" 616 | } elseif ($param -eq "suggestion_mode") { 617 | $validated_suggestion_mode = Validate-Suggestion-Mode $props[$param] 618 | $docker_cmd += " --suggestion_mode='$validated_suggestion_mode'" 619 | } elseif ($param -eq "locale") { 620 | $docker_cmd += " --locale='$($props[$param])'" 621 | } elseif ($param -eq "exclude_files") { 622 | $docker_cmd += " --exclude_files='$($props[$param])'" 623 | } elseif ($param -eq "exclude_draft_pr") { 624 | $docker_cmd += " --exclude_draft_pr=$($props[$param])" 625 | } elseif ($param -eq "dependency_check") { 626 | $validated_boolean = Validate-Boolean $props[$param] 627 | $docker_cmd += " --dependency_check.enabled=$validated_boolean" 628 | } elseif ($param -eq "code_feedback") { 629 | $validated_boolean = Validate-Boolean $props[$param] 630 | $docker_cmd += " --$param=$validated_boolean" 631 | } elseif ($param -eq "code_context") { 632 | #validate the code context boolean value 633 | $validated_boolean = Validate-Boolean $props[$param] 634 | $docker_cmd += " --$param=$validated_boolean" 635 | } elseif ($param -eq "mode") { 636 | Validate-Mode $props[$param] 637 | $docker_cmd += " --$param=$($props[$param])" 638 | } elseif ($param -eq "env") { 639 | Validate-Env $props[$param] 640 | $docker_cmd += " --$param=$($props[$param])" 641 | } elseif ($param -eq "cli_path") { 642 | $cli_dir = Check-CliDirectory $($props[$param]) 643 | $docker_init_cmd += " -v ${cli_dir}:/cli_dir" 644 | } elseif ($param -eq "output_path") { 645 | if ($($props[$param]) -ne $null -and $($props[$param]) -ne "") { 646 | $output_path = Check-OutputDirectory $($props[$param]) 647 | if ($output_path -ne $null -and $output_path -ne "") { 648 | $docker_init_cmd += " -v '${output_path}:/output_path'" 649 | $docker_cmd += " --$param=/output_path" 650 | } 651 | } 652 | } elseif ($param -eq "review_comments") { 653 | $review_comments = Validate-ReviewComments $props[$param] 654 | $docker_cmd += " --$param=$review_comments" 655 | } elseif ($param -eq "cr_event_type") { 656 | $crEventType = ValidateCrEventType $props[$param] 657 | } elseif ($param -eq "posting_to_pr") { 658 | $postingToPr = ValidatePostingToPr $props[$param] 659 | } elseif ($param -eq "support_email") { 660 | $docker_cmd += " --support_email='$( $props[$param] )'" 661 | } else { 662 | $docker_cmd += " --$param=$($props[$param])" 663 | } 664 | } 665 | } 666 | $docker_cmd += " --cr_event_type=$crEventType" 667 | $docker_cmd += " --posting_to_pr=$postingToPr" 668 | $docker_cmd = $docker_init_cmd + $docker_cmd 669 | 670 | function Encrypt-GitSecret { 671 | param ( 672 | [string]$key, 673 | [string]$plaintext 674 | ) 675 | 676 | # Convert key to hex 677 | $hexKey = [BitConverter]::ToString([Text.Encoding]::UTF8.GetBytes($key)).Replace("-", "").ToLower() 678 | 679 | # Generate IV (Initialization Vector) 680 | $ivBytes = New-Object byte[] 16 681 | [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($ivBytes) 682 | $iv = [Convert]::ToBase64String($ivBytes) 683 | $ivHex = [BitConverter]::ToString($ivBytes).Replace("-", "").ToLower() 684 | 685 | $ciphertext = "$plaintext" | openssl enc -aes-256-cfb -a -K "$hexKey" -iv "$ivHex" -base64 686 | 687 | # Concatenate IV and ciphertext and encode with base64 688 | $encryptedText = $ivHex + "$ciphertext" -replace " ", "" -replace "`r`n", "" -replace "`n", "" -replace "`r", "" 689 | 690 | # Output the encrypted text 691 | return $encryptedText 692 | } 693 | 694 | $docker_run_command_log = $docker_cmd 695 | $param_bito_access_key = "bito_cli.bito.access_key" 696 | $param_git_access_token = "git.access_token" 697 | $docker_enc_params= 698 | 699 | if ($mode -eq "server") { 700 | if (-not([string]::IsNullOrEmpty($props[$param_bito_access_key])) -and -not([string]::IsNullOrEmpty($props[$param_git_access_token]))) { 701 | $git_secret = "$($props[$param_bito_access_key])@#~^$($props[$param_git_access_token])" 702 | $encryption_key = [System.Convert]::ToBase64String((1..32 | ForEach-Object { [byte](Get-Random -Minimum 0 -Maximum 256) })) 703 | $git_secret_encrypted = Encrypt-GitSecret -key $encryption_key -plaintext $git_secret 704 | $docker_enc_params=" --git.secret=$git_secret_encrypted --encryption_key=$encryption_key" 705 | $docker_cmd += " ${docker_enc_params}" 706 | 707 | Write-Host "Use below as Gitlab and Github Webhook secret:" 708 | Write-Host $git_secret_encrypted 709 | Write-Host 710 | } 711 | 712 | $docker_cmd += " > ""$BITOCRACID""" 713 | } 714 | 715 | # Execute the docker command 716 | Write-Host "Running command: $($docker_pull)" 717 | Invoke-Expression $docker_pull 718 | 719 | if ($LASTEXITCODE -eq 0) { 720 | Write-Host "Running command: $($docker_run_command_log)" 721 | Invoke-Expression $docker_cmd 722 | 723 | if ($LASTEXITCODE -eq 0 -and $mode -eq "server") { 724 | Display-DockerUrl 725 | $continerIdLine = "export CONTAINER_ID=" 726 | $continerIdLine += (Get-Content "$BITOCRACID") 727 | Set-Content -Path "$BITOCRALOCKFILE" -Value "$continerIdLine" 728 | Remove-Item -Path "$BITOCRACID" -Force 729 | } 730 | } 731 | 732 | -------------------------------------------------------------------------------- /cra-scripts/bito-cra.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #set -x 3 | 4 | # Variables for temp files. 5 | BITOAIDIR="$HOME/.bitoai" 6 | mkdir -p $BITOAIDIR 7 | BITOCRALOCKFILE=$BITOAIDIR/bitocra.lock 8 | BITOCRACID=$BITOAIDIR/bitocra.cid 9 | 10 | validate_bash_version() { 11 | # Get the Bash version 12 | bash_version=$(bash --version | head -n 1 | awk '{print $4}') 13 | 14 | # Extract the major version number 15 | major_version=$(echo "$bash_version" | awk -F '.' '{print $1}') 16 | 17 | # Check if the Bash version is less than 4.x 18 | if [[ $major_version -lt 4 ]]; then 19 | echo "Bash version $bash_version is not supported. Please upgrade to Bash 4.x or higher." 20 | exit 1 21 | fi 22 | } 23 | 24 | validate_docker_version() { 25 | # Get the Docker version 26 | docker_version=$(docker version --format '{{.Server.Version}}') 27 | 28 | # Extract the major version number 29 | major_version=$(echo "$docker_version" | awk -F '.' '{print $1}') 30 | 31 | # Check if the Docker version is less than 20.x 32 | if [[ $major_version -lt 20 ]]; then 33 | echo "Docker version $docker_version is not supported. Please upgrade to Docker 20.x or higher." 34 | exit 1 35 | fi 36 | } 37 | 38 | # Function to validate a URL (basic validation) 39 | validate_url() { 40 | local url="$1" 41 | if ! [[ "$url" =~ ^https?:// ]]; then 42 | echo "Invalid URL. Please enter a valid URL." 43 | exit 1 44 | fi 45 | } 46 | 47 | # Function to validate a git provider value i.e. either GITLAB or GITHUB 48 | validate_git_provider() { 49 | local git_provider_val=$(echo "$1" | tr '[:lower:]' '[:upper:]') 50 | 51 | if [ "$git_provider_val" == "GITLAB" ] || [ "$git_provider_val" == "GITHUB" ] || [ "$git_provider_val" == "BITBUCKET" ]; then 52 | echo $git_provider_val 53 | else 54 | echo "Invalid git provider value. Please enter either GITLAB or GITHUB or BITBUCKET." 55 | exit 1 56 | fi 57 | } 58 | 59 | # Function to validate a boolean value i.e. string compare against "True" or "False" 60 | validate_boolean() { 61 | local boolean_val="$(echo "$1" | awk '{print tolower($0)}')" 62 | if [ "$boolean_val" == "true" ]; then 63 | echo "True" 64 | elif [ "$boolean_val" == "false" ]; then 65 | echo "False" 66 | else 67 | echo "Invalid boolean value. Please enter either True or False." 68 | exit 1 69 | fi 70 | } 71 | 72 | # Function to validate suggestion mode 73 | validate_suggestion_mode() { 74 | local suggestion_mode="$(echo "$1" | awk '{print tolower($0)}')" 75 | if [ "$suggestion_mode" == "comprehensive" ]; then 76 | echo "comprehensive" 77 | else 78 | echo "essential" 79 | fi 80 | } 81 | 82 | # Function to validate a mode value i.e. cli or server 83 | validate_mode() { 84 | local mode_val="$1" 85 | if [ "$mode_val" == "cli" ] || [ "$mode_val" == "server" ]; then 86 | #echo "Valid mode value" 87 | echo 88 | else 89 | echo "Invalid mode value. Please enter either cli or server." 90 | exit 1 91 | fi 92 | } 93 | 94 | # Function to validate a env value i.e. prod or staging 95 | validate_env() { 96 | local env="$1" 97 | if [ "$env" == "prod" ] || [ "$env" == "staging" ] || [ "$env" == "preprod" ]; then 98 | #echo "Valid mode value" 99 | echo 100 | else 101 | echo "Invalid mode value. Please enter either prod or staging or preprod." 102 | exit 1 103 | fi 104 | } 105 | 106 | cr_event_type="automated" 107 | validate_cr_event_type() { 108 | local cr_event_type_val="$1" 109 | if [ "$cr_event_type_val" == "manual" ]; then 110 | cr_event_type=$cr_event_type_val 111 | echo 112 | fi 113 | } 114 | 115 | posting_to_pr="True" 116 | validate_posting_to_pr() { 117 | local boolean_val="$(echo "$1" | awk '{print tolower($0)}')" 118 | if [ "$boolean_val" == "true" ]; then 119 | posting_to_pr="True" 120 | elif [ "$boolean_val" == "false" ]; then 121 | posting_to_pr="False" 122 | fi 123 | } 124 | 125 | # Function to validate a review_comments vallue i.e. 1 mapped to "FULLPOST" or 2 mapped to "INLINE" 126 | validate_review_comments() { 127 | local review_comments="$1" 128 | if [ "$review_comments" == "1" ]; then 129 | echo "FULLPOST" 130 | elif [ "$review_comments" == "2" ]; then 131 | echo "INLINE" 132 | else 133 | echo "Invalid review comments value. Please enter either 1 or 2." 134 | exit 1 135 | fi 136 | } 137 | 138 | # Function to display URL using IP address and port 139 | # Run docker ps -l command and store the output 140 | display_docker_url() { 141 | container_info=$(docker ps -l | tail -n +2) 142 | 143 | # Extract IP address and port number using awk 144 | ip_address=$(echo "$container_info" | awk 'NR>0 {print $(NF-1)}' | cut -d':' -f1) 145 | #container_id=$(echo "$container_info" | awk 'NR>0 {print $1}') 146 | #ip_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container_id") 147 | #if [[ $(uname) == "Darwin" ]]; then 148 | # ip_address=$(ifconfig | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}') 149 | #else 150 | # ip_address=$(ip route get 1 | awk '{print $NF;exit}') 151 | #fi 152 | if [ "$ip_address" == "0.0.0.0" ]; then 153 | ip_address="127.0.0.1" 154 | fi 155 | port_number=$(echo "$container_info" | awk 'NR>0 {print $(NF-1)}' | cut -d'-' -f1 | cut -d':' -f2) 156 | 157 | # Print the IP address and port number 158 | #echo "IP Address: $ip_address" 159 | #echo "Port Number: $port_number" 160 | 161 | if [ -n "$ip_address" ] && [ -n "$port_number" ]; then 162 | # Construct the URL 163 | url="http://${ip_address}:${port_number}/" 164 | 165 | # Print the URL 166 | echo "" 167 | echo "Code Review Agent URL: $url" 168 | echo "Note: Use above URL to configure GITLAB/GITHUB webhook by replacing IP adderss with the IP address or Domain Name of your server." 169 | fi 170 | } 171 | 172 | display_usage() { 173 | echo "Invalid command to execute Code Review Agent:" 174 | echo 175 | echo "Usage-1: $0 " 176 | echo "Usage-2: $0 service start | restart " 177 | echo "Usage-3: $0 service stop" 178 | echo "Usage-4: $0 service status" 179 | echo "Usage-5: $0 pr_url=" 180 | } 181 | 182 | check_properties_file() { 183 | local prop_file="$1" 184 | if [ -z "$prop_file" ]; then 185 | echo "Properties file not provided!" 186 | return 1 187 | fi 188 | if [ ! -f "$prop_file" ]; then 189 | echo "Properties file not found!" 190 | return 1 191 | else 192 | echo $prop_file 193 | return 0 194 | fi 195 | } 196 | 197 | check_action_directory() { 198 | local action_dir="$1" 199 | if [ -z "$action_dir" ]; then 200 | echo "Action directory not provided!" 201 | return 1 202 | fi 203 | if [ ! -d "$action_dir" ]; then 204 | echo "Action directory not found!" 205 | return 1 206 | else 207 | echo $action_dir 208 | return 0 209 | fi 210 | } 211 | 212 | check_cli_directory() { 213 | local cli_dir="$1" 214 | if [ -z "$cli_dir" ]; then 215 | echo "cli directory not provided!" 216 | return 1 217 | fi 218 | if [ ! -d "$cli_dir" ]; then 219 | echo "cli directory not found!" 220 | return 1 221 | else 222 | echo $cli_dir 223 | return 0 224 | fi 225 | } 226 | 227 | check_output_directory() { 228 | local output_path="$1" 229 | if [ ! -d "$output_path" ]; then 230 | echo "output path directory not found!" 231 | return 1 232 | else 233 | echo "Output Path: $output_path" 234 | return 0 235 | fi 236 | } 237 | 238 | stop_cra() { 239 | if test -f "$BITOCRALOCKFILE"; then 240 | echo "Stopping the CRA..." 241 | source "$BITOCRALOCKFILE" 242 | docker stop "$CONTAINER_ID" 243 | RET_VAL=`echo $?` 244 | if [ $RET_VAL -ne 0 ]; then 245 | echo "Could not stop CRA" 246 | exit 1 247 | fi 248 | rm -rf "$BITOCRALOCKFILE" 249 | else 250 | echo "CRA is not running." 251 | fi 252 | } 253 | 254 | check_cra() { 255 | if test -f "$BITOCRALOCKFILE"; then 256 | echo "CRA is running." 257 | else 258 | echo "CRA is not running." 259 | fi 260 | } 261 | 262 | # Check if a properties file is provided as an argument 263 | if [ "$#" -lt 1 ]; then 264 | display_usage 265 | exit 1 266 | fi 267 | 268 | properties_file= 269 | action_directory= 270 | force_mode= 271 | pr_url_arg= 272 | 273 | process_pr_url_or_action_dir_param() { 274 | local param="$1" 275 | 276 | if [[ "$param" == pr_url=* ]]; then 277 | pr_url_arg="${param#*=}" 278 | else 279 | action_directory=$(check_action_directory "$param") 280 | if [ $? -ne 0 ]; then 281 | echo "Action directory not found!" 282 | exit 1 283 | fi 284 | fi 285 | } 286 | 287 | if [ "$#" -gt 1 ]; then 288 | if [ "$1" == "service" ]; then 289 | case "$2" in 290 | start) 291 | force_mode="server" 292 | properties_file=$(check_properties_file "$3") 293 | if [ $? -ne 0 ]; then 294 | echo "Properties file not found!" 295 | exit 1 296 | fi 297 | if test -f "$BITOCRALOCKFILE"; then 298 | echo "CRA is already running." 299 | exit 0 300 | fi 301 | 302 | echo "Starting the CRA..." 303 | 304 | # Note down the hidden parameter for action directory 305 | if [ "$#" -eq 4 ]; then 306 | action_directory=$(check_action_directory "$4") 307 | if [ $? -ne 0 ]; then 308 | echo "Action directory not found!" 309 | exit 1 310 | fi 311 | #echo "Action Diretory: $action_directory" 312 | fi 313 | ;; 314 | stop) 315 | stop_cra 316 | exit 0 317 | ;; 318 | restart) 319 | force_mode="server" 320 | properties_file=$(check_properties_file "$3") 321 | if [ $? -ne 0 ]; then 322 | echo "Properties file not found!" 323 | exit 1 324 | fi 325 | 326 | stop_cra 327 | echo "Starting the CRA..." 328 | 329 | # Note down the hidden parameter for action directory 330 | if [ "$#" -eq 4 ]; then 331 | action_directory=$(check_action_directory "$4") 332 | if [ $? -ne 0 ]; then 333 | echo "Action directory not found!" 334 | exit 1 335 | fi 336 | #echo "Action Diretory: $action_directory" 337 | fi 338 | ;; 339 | status) 340 | echo "Checking the CRA..." 341 | check_cra 342 | exit 0 343 | ;; 344 | *) 345 | display_usage 346 | exit 1 347 | ;; 348 | esac 349 | else 350 | # Load properties from file 351 | properties_file=$(check_properties_file "$1") 352 | if [ $? -ne 0 ]; then 353 | echo "Properties file not found!" 354 | exit 1 355 | fi 356 | 357 | # Note down the hidden parameter for action directory 358 | if [ "$#" -eq 2 ]; then 359 | #check if 2nd argument is like pr_url= then extract value else check the action_directory 360 | process_pr_url_or_action_dir_param "$2" 361 | fi 362 | 363 | if [ "$#" -eq 3 ]; then 364 | #check if 2nd argument is like pr_url= then extract value else check the action_directory 365 | process_pr_url_or_action_dir_param "$2" 366 | 367 | #check if 3rd argument is like pr_url= then extract value else check the action_directory 368 | process_pr_url_or_action_dir_param "$3" 369 | fi 370 | fi 371 | else 372 | # Load properties from file 373 | properties_file=$(check_properties_file "$1") 374 | if [ $? -ne 0 ]; then 375 | echo "Properties file not found!" 376 | exit 1 377 | fi 378 | fi 379 | 380 | #validate the bash versions and docker version 381 | validate_bash_version 382 | validate_docker_version 383 | 384 | # Read properties into an associative array 385 | declare -A props 386 | while IFS='=' read -r key value; do 387 | # Skip lines starting with # 388 | if [[ "$key" != \#* ]]; then 389 | props["$key"]="$value" 390 | fi 391 | done < "$properties_file" 392 | 393 | # Override pr_url if provided as an argument 394 | if [ -n "$pr_url_arg" ]; then 395 | props["pr_url"]="$pr_url_arg" 396 | fi 397 | 398 | # Function to ask for missing parameters 399 | ask_for_param() { 400 | local param_name=$1 401 | local param_value=${props[$param_name]} 402 | local exit_on_empty=$2 403 | 404 | if [ -z "$param_value" ]; then 405 | read -p "Enter value for $param_name: " param_value 406 | if [ -z $param_value ] && [ $exit_on_empty == "True" ]; then 407 | echo "No input provided for $param_name. Exiting." 408 | exit 1 409 | else 410 | props[$param_name]=$param_value 411 | fi 412 | fi 413 | } 414 | 415 | # Parameters that are required/optional in mode cli 416 | required_params_cli=( 417 | "mode" 418 | "pr_url" 419 | "git.provider" 420 | "git.access_token" 421 | "bito_cli.bito.access_key" 422 | "code_feedback" 423 | ) 424 | 425 | optional_params_cli=( 426 | "acceptable_suggestions_enabled" 427 | "review_comments" 428 | "static_analysis" 429 | "static_analysis_tool" 430 | "linters_feedback" 431 | "secret_scanner_feedback" 432 | "review_scope" 433 | "enable_default_branch" 434 | "exclude_branches" 435 | "include_source_branches" 436 | "include_target_branches" 437 | "post_as_request_changes" 438 | "suggestion_mode" 439 | "locale" 440 | "exclude_files" 441 | "exclude_draft_pr" 442 | "dependency_check" 443 | "dependency_check.snyk_auth_token" 444 | "cra_version" 445 | "env" 446 | "cli_path" 447 | "output_path" 448 | "git.domain" 449 | "code_context" 450 | "nexus_url" 451 | "cr_event_type" 452 | "posting_to_pr" 453 | "custom_rules.configured_ws_ids" 454 | "custom_rules.aws_access_key_id" 455 | "custom_rules.aws_secret_access_key" 456 | "custom_rules.region_name" 457 | "custom_rules.bucket_name" 458 | "custom_rules.aes_key" 459 | "support_email" 460 | ) 461 | 462 | # Parameters that are required/optional in mode server 463 | required_params_server=( 464 | "mode" 465 | "code_feedback" 466 | ) 467 | 468 | optional_params_server=( 469 | "git.provider" 470 | "git.access_token" 471 | "bito_cli.bito.access_key" 472 | "acceptable_suggestions_enabled" 473 | "review_comments" 474 | "static_analysis" 475 | "static_analysis_tool" 476 | "linters_feedback" 477 | "secret_scanner_feedback" 478 | "review_scope" 479 | "enable_default_branch" 480 | "exclude_branches" 481 | "include_source_branches" 482 | "include_target_branches" 483 | "post_as_request_changes" 484 | "suggestion_mode" 485 | "locale" 486 | "exclude_files" 487 | "exclude_draft_pr" 488 | "dependency_check" 489 | "dependency_check.snyk_auth_token" 490 | "server_port" 491 | "cra_version" 492 | "env" 493 | "cli_path" 494 | "git.domain" 495 | "code_context" 496 | "nexus_url" 497 | "cr_event_type" 498 | "custom_rules.configured_ws_ids" 499 | "custom_rules.aws_access_key_id" 500 | "custom_rules.aws_secret_access_key" 501 | "custom_rules.region_name" 502 | "custom_rules.bucket_name" 503 | "custom_rules.aes_key" 504 | "output_path" 505 | "support_email" 506 | ) 507 | 508 | bee_params=( 509 | "bee.path" 510 | "bee.actn_dir" 511 | ) 512 | 513 | props["bee.path"]="/automation-platform" 514 | if [ -z "$action_directory" ]; then 515 | props["bee.actn_dir"]="/automation-platform/default_bito_ad/bito_modules" 516 | else 517 | props["bee.actn_dir"]="/action_dir" 518 | fi 519 | 520 | # CRA Version 521 | cra_version="latest" 522 | 523 | # Docker pull command 524 | docker_pull='docker pull bitoai/cra:${cra_version}' 525 | nexus_url= 526 | 527 | # Construct the docker run command 528 | docker_init_cmd='docker run --rm -it' 529 | if [ ! -z "$action_directory" ]; then 530 | docker_init_cmd='docker run --rm -it -v $action_directory:/action_dir' 531 | fi 532 | 533 | required_params=("${required_params_cli[@]}") 534 | optional_params=("${optional_params_cli[@]}") 535 | mode="cli" 536 | param_mode="mode" 537 | command="review" 538 | docker_cmd="" 539 | #handle if CRA is starting in server mode using start command. 540 | if [ -n "$force_mode" ]; then 541 | props[$param_mode]="$force_mode" 542 | fi 543 | validate_mode "${props[$param_mode]}" 544 | if [ "${props[$param_mode]}" == "server" ]; then 545 | mode="server" 546 | required_params=("${required_params_server[@]}") 547 | optional_params=("${optional_params_server[@]}") 548 | # Append -p and -d parameter in docker command 549 | docker_cmd+=' -p ${server_port}:${server_port} -d' 550 | fi 551 | echo "Bito Code Review Agent is running as: ${mode}" 552 | echo "" 553 | #echo Required Parameters: "${required_params[@]}" 554 | #echo BEE Parameters: "${bee_params[@]}" 555 | #echo Optional Parameters: "${optional_params[@]}" 556 | 557 | # Append Docker Image and Tag Placeholder 558 | docker_repo="bitoai/cra" 559 | docker_cmd+=' ${docker_repo}:${cra_version}' 560 | 561 | 562 | # Ask for required parameters if they are not set 563 | for param in "${required_params[@]}"; do 564 | ask_for_param "$param" "True" 565 | done 566 | 567 | # Ask for optional parameters if they are not set 568 | for param in "${optional_params[@]}"; do 569 | if [ "$param" == "dependency_check.snyk_auth_token" ] && [ "${props["dependency_check"]}" == "True" ]; then 570 | ask_for_param "$param" "False" 571 | elif [ "$param" != "acceptable_suggestions_enabled" ] && [ "$param" != "dependency_check.snyk_auth_token" ] && [ "$param" != "env" ] && [ "$param" != "cli_path" ] && [ "$param" != "output_path" ] && [ "$param" != "static_analysis_tool" ] && [ "$param" != "linters_feedback" ] && [ "$param" != "secret_scanner_feedback" ] && [ "$param" != "enable_default_branch" ] && [ "$param" != "git.domain" ] && [ "$param" != "review_scope" ] && [ "$param" != "exclude_branches" ] && [ "$param" != "include_source_branches" ] && [ "$param" != "include_target_branches" ] && [ "$param" != "suggestion_mode" ] && [ "$param" != "locale" ] && [ "$param" != "nexus_url" ] && [ "$param" != "exclude_files" ] && [ "$param" != "exclude_draft_pr" ] && [ "$param" != "cr_event_type" ] && [ "$param" != "posting_to_pr" ] && [ "$param" != "custom_rules.configured_ws_ids" ] && [ "$param" != "custom_rules.aws_access_key_id" ] && [ "$param" != "custom_rules.aws_secret_access_key" ] && [ "$param" != "custom_rules.region_name" ] && [ "$param" != "custom_rules.bucket_name" ] && [ "$param" != "custom_rules.aes_key" ] && [ "$param" != "code_context_config.partial_timeout" ] && [ "$param" != "code_context_config.max_depth" ] && [ "$param" != "code_context_config.kill_timeout_sec" ] && [ "$param" != "post_as_request_changes" ] && [ "$param" != "support_email" ]; then 572 | ask_for_param "$param" "False" 573 | fi 574 | done 575 | 576 | # Append parameters to the docker command 577 | for param in "${required_params[@]}" "${bee_params[@]}" "${optional_params[@]}"; do 578 | 579 | if [ -n "${props[$param]}" ]; then 580 | 581 | if [ "$param" == "cra_version" ]; then 582 | #assign docker image name 583 | cra_version="${props[$param]}" 584 | elif [ "$param" == "server_port" ]; then 585 | #assign docker port 586 | server_port="${props[$param]}" 587 | docker_cmd+=" --$param=${props[$param]}" 588 | elif [ "$param" == "pr_url" ]; then 589 | #validate the URL 590 | trimmed_url=$(echo "${props[$param]}" | sed 's/^[ \t]*//;s/[ \t]*$//') 591 | validate_url $trimmed_url 592 | docker_cmd+=" --$param=${trimmed_url} --command='${command}' rest" 593 | elif [ "$param" == "git.provider" ]; then 594 | #validate the URL 595 | props[$param]=$(validate_git_provider "${props[$param]}") 596 | docker_cmd+=" --$param=${props[$param]}" 597 | elif [ "$param" == "static_analysis" ]; then 598 | #handle special case of static_analysis.fb_infer.enabled using static_analysis 599 | props[$param]=$(validate_boolean "${props[$param]}") 600 | docker_cmd+=" --static_analysis.fb_infer.enabled=${props[$param]}" 601 | elif [ "$param" == "static_analysis_tool" ]; then 602 | docker_cmd+=" --static_analysis_tool=${props[$param]}" 603 | elif [ "$param" == "linters_feedback" ]; then 604 | props[$param]=$(validate_boolean "${props[$param]}") 605 | docker_cmd+=" --linters_feedback=${props[$param]}" 606 | elif [ "$param" == "post_as_request_changes" ]; then 607 | props[$param]=$(validate_boolean "${props[$param]}") 608 | docker_cmd+=" --post_as_request_changes=${props[$param]}" 609 | elif [ "$param" == "secret_scanner_feedback" ]; then 610 | props[$param]=$(validate_boolean "${props[$param]}") 611 | docker_cmd+=" --secret_scanner_feedback=${props[$param]}" 612 | elif [ "$param" == "acceptable_suggestions_enabled" ]; then 613 | props[$param]=$(validate_boolean "${props[$param]}") 614 | docker_cmd+=" --acceptable_suggestions_enabled=${props[$param]}" 615 | elif [ "$param" == "review_scope" ]; then 616 | scopes=$(echo ${props[$param]} | sed 's/, */,/g') 617 | docker_cmd+=" --review_scope='[$scopes]'" 618 | elif [ "$param" == "enable_default_branch" ]; then 619 | props[$param]=$(validate_boolean "${props[$param]}") 620 | docker_cmd+=" --enable_default_branch=${props[$param]}" 621 | elif [ "$param" == "exclude_branches" ]; then 622 | docker_cmd+=" --exclude_branches='${props[$param]}'" 623 | elif [ "$param" == "include_source_branches" ]; then 624 | docker_cmd+=" --include_source_branches='${props[$param]}'" 625 | elif [ "$param" == "include_target_branches" ]; then 626 | docker_cmd+=" --include_target_branches='${props[$param]}'" 627 | elif [ "$param" == "suggestion_mode" ]; then 628 | props[$param]=$(validate_suggestion_mode "${props[$param]}") 629 | docker_cmd+=" --suggestion_mode='${props[$param]}'" 630 | elif [ "$param" == "locale" ]; then 631 | docker_cmd+=" --locale='${props[$param]}'" 632 | elif [ "$param" == "exclude_files" ]; then 633 | docker_cmd+=" --exclude_files='${props[$param]}'" 634 | elif [ "$param" == "exclude_draft_pr" ]; then 635 | docker_cmd+=" --exclude_draft_pr=${props[$param]}" 636 | elif [ "$param" == "dependency_check" ]; then 637 | #validate the dependency check boolean value 638 | props[$param]=$(validate_boolean "${props[$param]}") 639 | docker_cmd+=" --dependency_check.enabled=${props[$param]}" 640 | elif [ "$param" == "code_feedback" ]; then 641 | #validate the code feedback boolean value 642 | props[$param]=$(validate_boolean "${props[$param]}") 643 | docker_cmd+=" --$param=${props[$param]}" 644 | elif [ "$param" == "code_context" ]; then 645 | #validate the code context boolean value 646 | props[$param]=$(validate_boolean "${props[$param]}") 647 | docker_cmd+=" --$param=${props[$param]}" 648 | elif [ "$param" == "mode" ]; then 649 | validate_mode "${props[$param]}" 650 | docker_cmd+=" --$param=${props[$param]}" 651 | elif [ "$param" == "env" ]; then 652 | validate_env "${props[$param]}" 653 | docker_cmd+=" --$param=${props[$param]}" 654 | elif [ "$param" == "cli_path" ]; then 655 | check_cli_directory "${props[$param]}" 656 | cli_dir=${props[$param]} 657 | docker_init_cmd+=' -v $cli_dir:/cli_dir' 658 | elif [ "$param" == "output_path" ]; then 659 | if [ -n "${props[$param]}" ]; then 660 | check_output_directory "${props[$param]}" 661 | return_val=$? # Capture the return value of the check output directory 662 | if [ $return_val -eq 0 ]; then 663 | output_path=${props[$param]} 664 | docker_init_cmd+=' -v "$output_path":/output_path' 665 | docker_cmd+=" --$param=/output_path" 666 | fi 667 | fi 668 | elif [ "$param" == "review_comments" ]; then 669 | #validate the review comments value 670 | props[$param]=$(validate_review_comments "${props[$param]}") 671 | return_val=$? # Capture the return value of the check output directory 672 | if [ $return_val -eq 0 ]; then 673 | docker_cmd+=" --$param=${props[$param]}" 674 | else 675 | echo "Invalid value provided for review_comments. Exiting." 676 | exit 1 677 | fi 678 | elif [ "$param" == "nexus_url" ]; then 679 | nexus_url=$(echo "${props[$param]}" | sed 's/^[ \t]*//;s/[ \t]*$//') 680 | elif [ "$param" == "cr_event_type" ]; then 681 | validate_cr_event_type "${props[$param]}" 682 | elif [ "$param" == "posting_to_pr" ]; then 683 | validate_posting_to_pr "${props[$param]}" 684 | elif [ "$param" == "support_email" ]; then 685 | docker_cmd+=" --support_email='${props[$param]}'" 686 | else 687 | docker_cmd+=" --$param=${props[$param]}" 688 | fi 689 | 690 | fi 691 | done 692 | docker_cmd+=" --cr_event_type=${cr_event_type}" 693 | docker_cmd+=" --posting_to_pr=${posting_to_pr}" 694 | docker_cmd=$docker_init_cmd$docker_cmd 695 | docker_cmd+=' ${docker_enc_params}' 696 | 697 | # Function to encrypt text 698 | encrypt_git_secret() { 699 | local key=$1 700 | local plaintext=$2 701 | 702 | # Convert key to hex 703 | local hex_key=$(echo -n "$key" | xxd -p -c 256) 704 | 705 | # Generate IV (Initialization Vector) 706 | local iv=$(openssl rand -base64 16) 707 | iv="$(echo -n "$iv" | base64 -d | xxd -p -c 256)" 708 | 709 | # Encrypt plaintext 710 | local ciphertext=$(echo -n "$plaintext" | openssl enc -aes-256-cfb -a -K "$hex_key" -iv "$iv" -base64) 711 | 712 | # Concatenate IV and ciphertext and encode with base64 713 | local iv_ciphertext=$(echo -n "$iv")$(echo -n "$ciphertext") 714 | 715 | # Encode the concatenated result with base64 716 | local encrypted_text=$(echo -n "$iv_ciphertext" | tr -d '\n') 717 | 718 | echo "$encrypted_text" 719 | } 720 | 721 | param_bito_access_key="bito_cli.bito.access_key" 722 | param_git_access_token="git.access_token" 723 | 724 | docker_enc_params= 725 | if [ "$mode" == "server" ]; then 726 | if [ -n "${props[$param_bito_access_key]}" ] && [ -n "${props[$param_git_access_token]}" ]; then 727 | git_secret="${props[$param_bito_access_key]}@#~^${props[$param_git_access_token]}" 728 | encryption_key=$(openssl rand -base64 32) 729 | git_secret=$(encrypt_git_secret "$encryption_key" "$git_secret") 730 | docker_enc_params=" --git.secret=$git_secret --encryption_key=$encryption_key" 731 | 732 | echo "Use below as Gitlab and Github Webhook secret:" 733 | echo "$git_secret" 734 | echo 735 | fi 736 | 737 | docker_cmd+=" > \"$BITOCRACID\"" 738 | fi 739 | 740 | # Execute the docker command 741 | echo "Running command: $(eval echo $docker_pull)" 742 | eval "$docker_pull" 743 | 744 | 745 | if [ "$?" == 0 ] ; then 746 | echo "Docker image pulled successfully." 747 | else 748 | if [[ -n "$nexus_url" ]]; then 749 | nexus_pull='docker pull ${nexus_url}/cra:${cra_version}' 750 | echo "Running command: $(eval echo $nexus_pull)" 751 | eval "$nexus_pull" 752 | if [ "$?" == 0 ]; then 753 | docker_repo='${nexus_url}/cra' 754 | docker_repo=$(eval echo "$docker_repo") 755 | echo "Successfully pulled docker image from Nexus." 756 | else 757 | echo "Failed to pull docker image from Nexus." 758 | fi 759 | fi 760 | fi 761 | 762 | 763 | if [ "$?" == 0 ]; then 764 | echo "Running command: $(echo eval $docker_cmd)" 765 | eval "$docker_cmd" 766 | 767 | if [ "$?" == 0 ] && [ "$mode" == "server" ]; then 768 | display_docker_url 769 | printf "export CONTAINER_ID=" > "$BITOCRALOCKFILE" 770 | cat "$BITOCRACID" >> "$BITOCRALOCKFILE" 771 | rm -rf "$BITOCRACID" 772 | fi 773 | fi 774 | 775 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | # Arguments to run the Docker image 5 | echo $INPUT_PR 6 | echo $INPUT_COMMAND 7 | echo $INPUT_OPTIONS 8 | echo $EVENT_NAME 9 | 10 | if [ "$EVENT_NAME" = "pull_request" ]; then 11 | INPUT_OPTIONS="$INPUT_OPTIONS --cr_event_type=automated" 12 | else 13 | INPUT_OPTIONS="$INPUT_OPTIONS --cr_event_type=manual" 14 | fi 15 | 16 | # Function to remove spaces from the value 17 | remove_spaces() { 18 | echo "$1" | tr -d ' ' 19 | } 20 | 21 | # Function to convert a string to lowercase 22 | to_lowercase() { 23 | echo "$1" | tr '[:upper:]' '[:lower:]' 24 | } 25 | 26 | process_input_options() { 27 | local input="$1" 28 | local docker_cmd_args="" 29 | 30 | # Use sed to add newlines before each '--' to simplify processing 31 | local formatted_input=$(echo "$input" | sed 's/ --/\n--/g') 32 | 33 | docker_cmd_args=$(echo "$formatted_input" | while IFS= read -r line 34 | do 35 | # Extract key by cutting until the first '=' 36 | key=$(echo "$line" | cut -d'=' -f1) 37 | 38 | # Extract value by removing everything before the first '=' 39 | value=$(echo "$line" | cut -d'=' -f2-) 40 | 41 | # Check if the argument is --review_scope, --exclude_files, or --exclude_branches and remove spaces 42 | if [[ "$key" == "--review_scope" || "$key" == "--static_analysis_tool" ]]; then 43 | value=$(remove_spaces "$value") 44 | value=$(to_lowercase "$value") 45 | elif [[ "$key" == "--exclude_files" || "$key" == "--exclude_branches" ]]; then 46 | value=$(remove_spaces "$value") 47 | fi 48 | 49 | # Append to the modified arguments 50 | echo -n "$key=$value " 51 | done) 52 | 53 | # Return the docker command arguments 54 | echo "$docker_cmd_args" 55 | } 56 | 57 | # Process the input arguments and get the modified result 58 | docker_cmd_args=$(process_input_options "$INPUT_OPTIONS") 59 | echo "Docker Command Args: $docker_cmd_args" 60 | 61 | SUPPORTED_COMMANDS=("/review" "review") 62 | 63 | #INPUT_COMMAND=$(echo "$INPUT_COMMAND" | tr -d '[:space:]') 64 | INPUT_COMMAND=$(echo "$INPUT_COMMAND" | xargs) 65 | 66 | # Check if the command starts with any of the supported commands 67 | for command in "${SUPPORTED_COMMANDS[@]}"; do 68 | if [[ "$INPUT_COMMAND" =~ ^$command ]]; then 69 | valid_command=true 70 | break 71 | fi 72 | done 73 | 74 | 75 | # Run the Docker container from the specified image 76 | if [ "$valid_command" = true ]; then 77 | docker pull bitoai/cra:latest >&2 78 | exec docker run bitoai/cra:latest --mode=cli --pr_url $INPUT_PR --command "$INPUT_COMMAND" rest $docker_cmd_args 79 | else 80 | echo "$INPUT_COMMAND is not supported" 81 | exit 0 # Exit the script with a non-zero status code 82 | fi 83 | --------------------------------------------------------------------------------