├── .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 |
39 |
40 |
41 |
42 |
43 |
44 | Table of contents
45 |
46 | -
47 | About the project
48 |
49 | -
50 | Getting started
51 |
52 | -
53 | Why use AI for code review?
54 |
55 | -
56 | Key features
57 |
58 | -
59 | Screenshots
60 |
61 | -
62 | Need support? We're ready to assist!
63 |
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 | [](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 |
137 |
138 |
139 |
140 |
141 | ---
142 |
143 |
144 |
145 | ### Screenshot # 2
146 |
147 | > _Code review manually triggered using **/review** command._
148 |
149 |
150 |
151 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------