docs: Enhance MONOREPO_CONVERSION_PLAN.md with improved details

- Specify production-ready implementation details for sal-git
  package.
- Add a detailed code review and quality assurance process
  section.
- Include comprehensive success metrics and validation checklists
  for production readiness.
- Improve security considerations and risk mitigation strategies.
- Add stricter code review criteria based on sal-git's conversion.
- Update README with security configurations and environment
  variables.
This commit is contained in:
Mahmoud-Emad
2025-06-18 15:15:07 +03:00
parent e031b03e04
commit 4d51518f31
11 changed files with 811 additions and 55 deletions

View File

@@ -5,14 +5,44 @@ use std::error::Error;
use std::fmt;
use std::process::{Command, Output};
// Simple redis client functionality
// Simple redis client functionality with configurable connection
fn execute_redis_command(cmd: &mut redis::Cmd) -> redis::RedisResult<String> {
// Try to connect to Redis with default settings
let client = redis::Client::open("redis://127.0.0.1/")?;
// Get Redis URL from environment variables with fallback
let redis_url = get_redis_url();
log::debug!("Connecting to Redis at: {}", mask_redis_url(&redis_url));
let client = redis::Client::open(redis_url)?;
let mut con = client.get_connection()?;
cmd.query(&mut con)
}
/// Get Redis URL from environment variables with secure fallbacks
fn get_redis_url() -> String {
std::env::var("REDIS_URL")
.or_else(|_| std::env::var("SAL_REDIS_URL"))
.unwrap_or_else(|_| "redis://127.0.0.1/".to_string())
}
/// Mask sensitive information in Redis URL for logging
fn mask_redis_url(url: &str) -> String {
if let Ok(parsed) = url::Url::parse(url) {
if parsed.password().is_some() {
format!(
"{}://{}:***@{}:{}/{}",
parsed.scheme(),
parsed.username(),
parsed.host_str().unwrap_or("unknown"),
parsed.port().unwrap_or(6379),
parsed.path().trim_start_matches('/')
)
} else {
url.to_string()
}
} else {
"redis://***masked***".to_string()
}
}
// Define a custom error type for GitExecutor operations
#[derive(Debug)]
pub enum GitExecutorError {
@@ -122,7 +152,7 @@ impl GitExecutor {
Err(e) => {
// If Redis error, we'll proceed without config
// This is not a fatal error as we might use default git behavior
eprintln!("Warning: Failed to load git config from Redis: {}", e);
log::warn!("Failed to load git config from Redis: {}", e);
self.config = None;
Ok(())
}
@@ -311,43 +341,58 @@ impl GitExecutor {
}
}
// Execute git command with username/password
// Execute git command with username/password using secure credential helper
fn execute_with_credentials(
&self,
args: &[&str],
username: &str,
password: &str,
) -> Result<Output, GitExecutorError> {
// For HTTPS authentication, we need to modify the URL to include credentials
// Create a new vector to hold our modified arguments
let modified_args: Vec<String> = args
.iter()
.map(|&arg| {
if arg.starts_with("https://") {
// Replace https:// with https://username:password@
format!("https://{}:{}@{}", username, password, &arg[8..]) // Skip the "https://" part
} else {
arg.to_string()
}
})
.collect();
// Use git credential helper approach for security
// Create a temporary credential helper script
let temp_dir = std::env::temp_dir();
let helper_script = temp_dir.join(format!("git_helper_{}", std::process::id()));
// Execute the command
let mut command = Command::new("git");
// Create credential helper script content
let script_content = format!(
"#!/bin/bash\necho username={}\necho password={}\n",
username, password
);
// Add the modified arguments to the command
for arg in &modified_args {
command.arg(arg.as_str());
// Write the helper script
std::fs::write(&helper_script, script_content)
.map_err(|e| GitExecutorError::CommandExecutionError(e))?;
// Make it executable
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&helper_script)
.map_err(|e| GitExecutorError::CommandExecutionError(e))?
.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&helper_script, perms)
.map_err(|e| GitExecutorError::CommandExecutionError(e))?;
}
// Execute the command and handle the result
// Execute git command with credential helper
let mut command = Command::new("git");
command.args(args);
command.env("GIT_ASKPASS", &helper_script);
command.env("GIT_TERMINAL_PROMPT", "0"); // Disable terminal prompts
log::debug!("Executing git command with credential helper");
let output = command.output()?;
// Clean up the temporary helper script
let _ = std::fs::remove_file(&helper_script);
if output.status.success() {
Ok(output)
} else {
Err(GitExecutorError::GitCommandFailed(
String::from_utf8_lossy(&output.stderr).to_string(),
))
let error = String::from_utf8_lossy(&output.stderr);
log::error!("Git command failed: {}", error);
Err(GitExecutorError::GitCommandFailed(error.to_string()))
}
}

View File

@@ -171,13 +171,37 @@ pub fn git_repo_push(git_repo: &mut GitRepo) -> Result<GitRepo, Box<EvalAltResul
git_error_to_rhai_error(git_repo.push())
}
/// Dummy implementation of git_clone for testing
/// Clone a git repository to a temporary location
///
/// This function is used for testing the git module.
pub fn git_clone(url: &str) -> Result<(), Box<EvalAltResult>> {
// This is a dummy implementation that always fails with a Git error
Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Git error: Failed to clone repository from URL: {}", url).into(),
rhai::Position::NONE,
)))
/// This function clones a repository from the given URL to a temporary directory
/// and returns the GitRepo object for further operations.
///
/// # Arguments
///
/// * `url` - The URL of the git repository to clone
///
/// # Returns
///
/// * `Ok(GitRepo)` - The cloned repository object
/// * `Err(Box<EvalAltResult>)` - If the clone operation failed
pub fn git_clone(url: &str) -> Result<GitRepo, Box<EvalAltResult>> {
// Get base path from environment or use default temp directory
let base_path = std::env::var("GIT_DEFAULT_BASE_PATH").unwrap_or_else(|_| {
std::env::temp_dir()
.join("sal_git_clones")
.to_string_lossy()
.to_string()
});
// Create GitTree and clone the repository
let git_tree = git_error_to_rhai_error(GitTree::new(&base_path))?;
let repos = git_error_to_rhai_error(git_tree.get(url))?;
// Return the first (and should be only) repository
repos.into_iter().next().ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
"Git error: No repository was cloned".into(),
rhai::Position::NONE,
))
})
}