diff --git a/Dockerfile b/Dockerfile index 63c8afae..d4588fff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,10 @@ ARG BITBUCKET_CLIENT_SECRET ARG BITBUCKET_BASE_URL ARG INSTALL_ID ARG SERVER_URL +ARG GITHUB_APP_ID +ARG GITHUB_APP_CLIENT_ID +ARG GITHUB_APP_CLIENT_SECRET +ARG GITHUB_BASE_URL ENV GCP_CREDENTIALS=$GCP_CREDENTIALS @@ -29,9 +33,14 @@ ENV BITBUCKET_CLIENT_SECRET=$BITBUCKET_CLIENT_SECRET ENV BITBUCKET_BASE_URL=$BITBUCKET_BASE_URL ENV INSTALL_ID=$INSTALL_ID ENV SERVER_URL=$SERVER_URL +ENV GITHUB_APP_ID=$GITHUB_APP_ID +ENV GITHUB_APP_CLIENT_ID=$GITHUB_APP_CLIENT_ID +ENV GITHUB_APP_CLIENT_SECRET=$GITHUB_APP_CLIENT_SECRET +ENV GITHUB_BASE_URL=$GITHUB_BASE_URL COPY ./vibi-dpu/target/debug/vibi-dpu /app/vibi-dpu COPY ./pubsub-sa.json /app/pubsub-sa.json +COPY ./repo-profiler.pem /app/repo-profiler.pem # Start the Rust application CMD ["/app/vibi-dpu"] diff --git a/vibi-dpu/Cargo.toml b/vibi-dpu/Cargo.toml index ce1b20b7..cc4e22ec 100644 --- a/vibi-dpu/Cargo.toml +++ b/vibi-dpu/Cargo.toml @@ -34,5 +34,6 @@ rand = "0.8.5" chrono = "0.4.26" tonic = "0.9.2" once_cell = "1.18.0" # MIT +jsonwebtoken = "8.3.0" # MIT # todo - check all lib licences diff --git a/vibi-dpu/src/bitbucket/auth.rs b/vibi-dpu/src/bitbucket/auth.rs index 7c566c3b..a0451e03 100644 --- a/vibi-dpu/src/bitbucket/auth.rs +++ b/vibi-dpu/src/bitbucket/auth.rs @@ -2,9 +2,8 @@ use std::env; use std::str; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; -use reqwest::Client; -use super::config::get_client; use crate::db::auth::{save_auth_info_to_db, auth_info}; +use crate::utils::reqwest_client::get_client; use crate::utils::auth::AuthInfo; use crate::utils::review::Review; diff --git a/vibi-dpu/src/bitbucket/comment.rs b/vibi-dpu/src/bitbucket/comment.rs index 8ee80e8d..9569e7f7 100644 --- a/vibi-dpu/src/bitbucket/comment.rs +++ b/vibi-dpu/src/bitbucket/comment.rs @@ -1,15 +1,8 @@ -use std::{env, collections::HashMap}; - -use reqwest::{Response, header::{HeaderMap, HeaderValue}}; use serde::Serialize; -use serde_json::Value; -use crate::db::auth::auth_info; -use crate::db::user::get_workspace_user_from_db; use crate::utils::review::Review; -use crate::utils::user::BitbucketUser; - -use super::{config::{bitbucket_base_url, get_client, prepare_headers}}; +use crate::utils::reqwest_client::get_client; +use super::config::{bitbucket_base_url, prepare_headers}; #[derive(Serialize)] struct Comment { diff --git a/vibi-dpu/src/bitbucket/config.rs b/vibi-dpu/src/bitbucket/config.rs index dc678d37..79658c88 100644 --- a/vibi-dpu/src/bitbucket/config.rs +++ b/vibi-dpu/src/bitbucket/config.rs @@ -1,19 +1,8 @@ use std::{env, collections::HashMap}; -use std::error::Error; use reqwest::{Response, header::{HeaderMap, HeaderValue}}; use serde_json::Value; -use std::sync::Arc; -use once_cell::sync::Lazy; -use reqwest::Client; - -static CLIENT: Lazy> = Lazy::new(|| { - Arc::new(Client::new()) -}); - -pub fn get_client() -> Arc { - Arc::clone(&CLIENT) -} +use crate::utils::reqwest_client::get_client; pub fn bitbucket_base_url() -> String { env::var("BITBUCKET_BASE_URL").expect("BITBUCKET_BASE_URL must be set") diff --git a/vibi-dpu/src/bitbucket/prs.rs b/vibi-dpu/src/bitbucket/prs.rs index 0876af28..a75c73f5 100644 --- a/vibi-dpu/src/bitbucket/prs.rs +++ b/vibi-dpu/src/bitbucket/prs.rs @@ -2,9 +2,10 @@ use reqwest::header::HeaderMap; use serde_json::Value; use std::collections::HashMap; use std::str; -use crate::{utils::pr_info::PrInfo, db::prs::update_pr_info_in_db}; +use crate::utils::{pr_info::PrInfo, reqwest_client::get_client}; +use crate::db::prs::update_pr_info_in_db; -use super::config::{get_client, prepare_auth_headers, bitbucket_base_url}; +use super::config::{prepare_auth_headers, bitbucket_base_url}; pub async fn list_prs_bitbucket(repo_owner: &str, repo_name: &str, access_token: &str, state: &str) -> Option> { let headers_opt = prepare_auth_headers(access_token); diff --git a/vibi-dpu/src/bitbucket/reviewer.rs b/vibi-dpu/src/bitbucket/reviewer.rs index dba418eb..a893ec66 100644 --- a/vibi-dpu/src/bitbucket/reviewer.rs +++ b/vibi-dpu/src/bitbucket/reviewer.rs @@ -3,8 +3,9 @@ use serde_json::Value; use crate::utils::review::Review; use crate::utils::user::BitbucketUser; +use crate::utils::reqwest_client::get_client; -use super::config::{get_client, prepare_headers}; +use super::config::prepare_headers; pub async fn add_reviewers(user: &BitbucketUser, review: &Review, access_token: &str) { let url = prepare_get_prinfo_url(review.repo_owner(), review.repo_name(), review.id()); diff --git a/vibi-dpu/src/bitbucket/webhook.rs b/vibi-dpu/src/bitbucket/webhook.rs index b6a8f87d..10a51c8b 100644 --- a/vibi-dpu/src/bitbucket/webhook.rs +++ b/vibi-dpu/src/bitbucket/webhook.rs @@ -4,8 +4,8 @@ use reqwest::{header::HeaderValue, Response, Error}; use serde_json::json; use crate::{db::webhook::save_webhook_to_db, utils::webhook::{Webhook, WebhookResponse}, bitbucket::config::{bitbucket_base_url, get_api_values}}; - -use super::config::{get_client, prepare_auth_headers}; +use crate::utils::reqwest_client::get_client; +use super::config::prepare_auth_headers; pub async fn get_webhooks_in_repo(workspace_slug: &str, repo_slug: &str, access_token: &str) -> Vec { diff --git a/vibi-dpu/src/core/bitbucket/mod.rs b/vibi-dpu/src/core/bitbucket/mod.rs new file mode 100644 index 00000000..7633fd0b --- /dev/null +++ b/vibi-dpu/src/core/bitbucket/mod.rs @@ -0,0 +1 @@ +pub mod setup; \ No newline at end of file diff --git a/vibi-dpu/src/core/setup.rs b/vibi-dpu/src/core/bitbucket/setup.rs similarity index 99% rename from vibi-dpu/src/core/setup.rs rename to vibi-dpu/src/core/bitbucket/setup.rs index efd9ef7d..2b153b69 100644 --- a/vibi-dpu/src/core/setup.rs +++ b/vibi-dpu/src/core/bitbucket/setup.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use tokio::{task, fs}; use crate::bitbucket::auth::get_access_token_from_bitbucket; -use crate::bitbucket::config::get_client; +use crate::utils::reqwest_client::get_client; use crate::bitbucket::repo::get_workspace_repos; use crate::bitbucket::workspace::get_bitbucket_workspaces; use crate::bitbucket::webhook::{get_webhooks_in_repo, add_webhook}; @@ -94,7 +94,6 @@ pub async fn handle_install_bitbucket(installation_code: &str) { send_setup_info(&pubreqs).await; } - async fn send_setup_info(setup_info: &Vec) { let installation_id = env::var("INSTALL_ID") .expect("INSTALL_ID must be set"); diff --git a/vibi-dpu/src/core/github/mod.rs b/vibi-dpu/src/core/github/mod.rs new file mode 100644 index 00000000..7633fd0b --- /dev/null +++ b/vibi-dpu/src/core/github/mod.rs @@ -0,0 +1 @@ +pub mod setup; \ No newline at end of file diff --git a/vibi-dpu/src/core/github/setup.rs b/vibi-dpu/src/core/github/setup.rs new file mode 100644 index 00000000..90af1333 --- /dev/null +++ b/vibi-dpu/src/core/github/setup.rs @@ -0,0 +1,46 @@ +// setup_gh.rs + +use crate::github::auth::fetch_access_token; // Import shared utilities +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use tokio::task; + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct SetupInfoGithub { + provider: String, + owner: String, + repos: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct PublishRequestGithub { + installationId: String, + info: Vec, +} + +pub async fn handle_install_github(installation_code: &str) { + // TODO: Implement the logic to handle GitHub installation + + // For example: + // 1. Get access token from GitHub using the installation code + let auth_info = fetch_access_token(installation_code).await; + println!("[handle_install_github] auth_info = {:?}", &auth_info); + // 2. Fetch user repositories and other necessary data + // 3. Process webhooks or other setup tasks + // 4. Send setup info or any other post-processing +} + +async fn get_github_repositories(access_token: &str) -> Vec { + // TODO: Implement the logic to fetch user repositories from GitHub + Vec::new() +} + +async fn process_webhooks_github(repo_name: String, access_token: String) { + // TODO: Implement the logic to process GitHub webhooks +} + +async fn send_setup_info_github(setup_info: &Vec) { + // TODO: Implement the logic to send setup info for GitHub +} + +// Add other necessary functions and utilities specific to GitHub setup diff --git a/vibi-dpu/src/core/mod.rs b/vibi-dpu/src/core/mod.rs index 1f3467c7..d6dd6fa2 100644 --- a/vibi-dpu/src/core/mod.rs +++ b/vibi-dpu/src/core/mod.rs @@ -1,3 +1,5 @@ -pub mod setup; pub mod review; -mod coverage; \ No newline at end of file +mod coverage; + +pub mod bitbucket; +pub mod github; \ No newline at end of file diff --git a/vibi-dpu/src/core/review.rs b/vibi-dpu/src/core/review.rs index 6ed6a07c..789a161c 100644 --- a/vibi-dpu/src/core/review.rs +++ b/vibi-dpu/src/core/review.rs @@ -11,12 +11,12 @@ use crate::{ get_excluded_files, generate_diff, process_diffmap, - generate_blame}}, + generate_blame}, + reqwest_client::get_client}, db::{hunk::{get_hunk_from_db, store_hunkmap_to_db}, repo::get_clone_url_clone_dir, review::save_review_to_db, repo_config::save_repo_config_to_db}, - bitbucket::config::get_client, core::coverage::process_coverage}; pub async fn process_review(message_data: &Vec) { diff --git a/vibi-dpu/src/github/auth.rs b/vibi-dpu/src/github/auth.rs new file mode 100644 index 00000000..a5818b33 --- /dev/null +++ b/vibi-dpu/src/github/auth.rs @@ -0,0 +1,89 @@ +use jsonwebtoken::{encode, Header, EncodingKey, Algorithm}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::env; +use chrono::{Utc, Duration}; +use std::fs; +use crate::{utils::reqwest_client::get_client, utils::auth::AuthInfo}; + +#[derive(Debug, Serialize, Deserialize)] +struct AccessTokenResponse { + token: String, + // Add other fields if necessary +} + +#[derive(Debug, Serialize)] +struct Claims { + iat: i64, + exp: i64, + iss: String, +} + +fn generate_jwt(github_app_id: &str) -> Option { + let pem_file_path = "/app/repoprofiler_private.pem"; + let pem_data_res = fs::read(pem_file_path); + + if pem_data_res.is_err() { + let pem_data_err = pem_data_res.expect_err("No error in reading pem file"); + println!("Error reading pem file: {:?}", pem_data_err); + return None; + } + let pem_data = pem_data_res.expect("Error reading pem file"); + + let my_claims = Claims { + iat: Utc::now().timestamp(), + exp: (Utc::now() + Duration::minutes(5)).timestamp(), + iss: github_app_id.to_string(), + }; + + let encoding_key = EncodingKey::from_rsa_pem(&pem_data); + if encoding_key.is_err() { + println!("Error creating encoding key"); + return None; + } + + let token_res = encode(&Header::new(Algorithm::RS256), &my_claims, &encoding_key.unwrap()); + if token_res.is_err() { + let token_err = token_res.expect_err("No error in fetching token"); + println!("Error encoding JWT: {:?}", token_err); + return None; + }; + let token = token_res.expect("Error encoding JWT"); + Some(token) +} + +pub async fn fetch_access_token(installation_id: &str) -> Option { + let github_app_id = env::var("GITHUB_APP_ID"); + let github_app_id_str = github_app_id.expect("GITHUB_APP_ID must be set"); + let jwt_token = generate_jwt(&github_app_id_str).expect("Error generating JWT"); + + let client = get_client(); + let response = client.post(&format!("https://api.github.com/app/installations/{}/access_tokens", installation_id)) + .header("Accept", "application/vnd.github+json") + .header("Authorization", format!("Bearer {}", jwt_token)) + .header("User-Agent", "Vibinex code review Test App") + .send() + .await; + if response.is_err() { + let e = response.expect_err("No error in response"); + eprintln!("error in calling github api : {:?}", e); + return None; + } + let response_access_token = response.expect("Uncaught error in reponse"); + if !response_access_token.status().is_success() { + println!( + "Failed to exchange code for access token. Status code: {}, Response content: {:?}", + response_access_token.status(), + response_access_token.text().await + ); + return None; + } + let parse_res = response_access_token.json().await ; + if parse_res.is_err() { + let e = parse_res.expect_err("No error in parse_res for AuthInfo"); + eprintln!("error deserializing AuthInfo: {:?}", e); + return None; + } + let response_json: Value = parse_res.expect("Uncaught error in parse_res for AuthInfo"); + return Some(response_json); +} \ No newline at end of file diff --git a/vibi-dpu/src/github/mod.rs b/vibi-dpu/src/github/mod.rs new file mode 100644 index 00000000..0e4a05d5 --- /dev/null +++ b/vibi-dpu/src/github/mod.rs @@ -0,0 +1 @@ +pub mod auth; diff --git a/vibi-dpu/src/main.rs b/vibi-dpu/src/main.rs index 665b5a75..7d0bf040 100644 --- a/vibi-dpu/src/main.rs +++ b/vibi-dpu/src/main.rs @@ -3,6 +3,7 @@ mod pubsub; mod db; mod core; mod bitbucket; +mod github; mod utils; #[tokio::main] diff --git a/vibi-dpu/src/pubsub/listener.rs b/vibi-dpu/src/pubsub/listener.rs index bd941726..0ed75b83 100644 --- a/vibi-dpu/src/pubsub/listener.rs +++ b/vibi-dpu/src/pubsub/listener.rs @@ -13,7 +13,8 @@ use std::collections::VecDeque; use sha256::digest; use tonic::Code; use crate::db::prs::process_and_update_pr_if_different; -use crate::core::{setup::handle_install_bitbucket, review::process_review}; +use crate::core::review::process_review; +use crate::core::bitbucket::setup::handle_install_bitbucket; #[derive(Debug, Deserialize)] struct InstallCallback { diff --git a/vibi-dpu/src/utils/mod.rs b/vibi-dpu/src/utils/mod.rs index 8b670b9d..7ca0d54f 100644 --- a/vibi-dpu/src/utils/mod.rs +++ b/vibi-dpu/src/utils/mod.rs @@ -8,4 +8,5 @@ pub mod gitops; pub mod user; pub mod lineitem; pub mod repo_config; -pub mod pr_info; \ No newline at end of file +pub mod pr_info; +pub mod reqwest_client; \ No newline at end of file diff --git a/vibi-dpu/src/utils/reqwest_client.rs b/vibi-dpu/src/utils/reqwest_client.rs new file mode 100644 index 00000000..c442beb5 --- /dev/null +++ b/vibi-dpu/src/utils/reqwest_client.rs @@ -0,0 +1,11 @@ +use std::sync::Arc; +use once_cell::sync::Lazy; +use reqwest::Client; + +static CLIENT: Lazy> = Lazy::new(|| { + Arc::new(Client::new()) +}); + +pub fn get_client() -> Arc { + Arc::clone(&CLIENT) +} \ No newline at end of file