feat(kube): implement 44 new Rust K8s commands + helm binary support
New list commands: list_replicationcontrollers, list_poddisruptionbudgets, list_priorityclasses, list_runtimeclasses, list_leases, list_mutatingwebhookconfigurations, list_validatingwebhookconfigurations, list_endpoints, list_endpointslices, list_ingressclasses, list_namespaces_resource, list_crds, list_custom_resources New action commands: force_delete_resource, describe_resource, get_resource_yaml, attach_pod, restart_statefulset, restart_daemonset, scale_statefulset, scale_replicaset, scale_replicationcontroller, suspend_cronjob, resume_cronjob, trigger_cronjob, create_namespace, delete_namespace Log streaming: stream_pod_logs (tokio task + Tauri events), stop_log_stream Helm: helm_list_repos, helm_add_repo, helm_update_repos, helm_search_repo, helm_list_releases, helm_uninstall, helm_rollback Infrastructure: shell/helm.rs locate_helm(), scripts/download-helm.sh, AppState.log_streams for stream lifecycle management 363/363 tests passing, zero clippy warnings Co-Authored-By: TFTSR Engineering <noreply@tftsr.com>
This commit is contained in:
parent
58edc75ab5
commit
9c9ca16966
58
scripts/download-helm.sh
Normal file
58
scripts/download-helm.sh
Normal file
@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
HELM_VERSION="v3.17.0"
|
||||
BINARIES_DIR="src-tauri/binaries"
|
||||
|
||||
echo "Downloading helm binaries version ${HELM_VERSION}..."
|
||||
|
||||
mkdir -p "$BINARIES_DIR"
|
||||
|
||||
# Helm tarballs extract to {os}-{arch}/helm (or helm.exe on Windows)
|
||||
|
||||
echo "Downloading helm for Linux x86_64..."
|
||||
TMPDIR=$(mktemp -d)
|
||||
curl -L -o "$TMPDIR/helm-linux-amd64.tar.gz" \
|
||||
"https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
tar -xzf "$TMPDIR/helm-linux-amd64.tar.gz" -C "$TMPDIR"
|
||||
cp "$TMPDIR/linux-amd64/helm" "$BINARIES_DIR/helm-x86_64-unknown-linux-gnu"
|
||||
rm -rf "$TMPDIR"
|
||||
|
||||
echo "Downloading helm for Linux aarch64..."
|
||||
TMPDIR=$(mktemp -d)
|
||||
curl -L -o "$TMPDIR/helm-linux-arm64.tar.gz" \
|
||||
"https://get.helm.sh/helm-${HELM_VERSION}-linux-arm64.tar.gz"
|
||||
tar -xzf "$TMPDIR/helm-linux-arm64.tar.gz" -C "$TMPDIR"
|
||||
cp "$TMPDIR/linux-arm64/helm" "$BINARIES_DIR/helm-aarch64-unknown-linux-gnu"
|
||||
rm -rf "$TMPDIR"
|
||||
|
||||
echo "Downloading helm for macOS x86_64..."
|
||||
TMPDIR=$(mktemp -d)
|
||||
curl -L -o "$TMPDIR/helm-darwin-amd64.tar.gz" \
|
||||
"https://get.helm.sh/helm-${HELM_VERSION}-darwin-amd64.tar.gz"
|
||||
tar -xzf "$TMPDIR/helm-darwin-amd64.tar.gz" -C "$TMPDIR"
|
||||
cp "$TMPDIR/darwin-amd64/helm" "$BINARIES_DIR/helm-x86_64-apple-darwin"
|
||||
rm -rf "$TMPDIR"
|
||||
|
||||
echo "Downloading helm for macOS aarch64..."
|
||||
TMPDIR=$(mktemp -d)
|
||||
curl -L -o "$TMPDIR/helm-darwin-arm64.tar.gz" \
|
||||
"https://get.helm.sh/helm-${HELM_VERSION}-darwin-arm64.tar.gz"
|
||||
tar -xzf "$TMPDIR/helm-darwin-arm64.tar.gz" -C "$TMPDIR"
|
||||
cp "$TMPDIR/darwin-arm64/helm" "$BINARIES_DIR/helm-aarch64-apple-darwin"
|
||||
rm -rf "$TMPDIR"
|
||||
|
||||
echo "Downloading helm for Windows x86_64..."
|
||||
TMPDIR=$(mktemp -d)
|
||||
curl -L -o "$TMPDIR/helm-windows-amd64.zip" \
|
||||
"https://get.helm.sh/helm-${HELM_VERSION}-windows-amd64.zip"
|
||||
unzip -q "$TMPDIR/helm-windows-amd64.zip" -d "$TMPDIR"
|
||||
cp "$TMPDIR/windows-amd64/helm.exe" "$BINARIES_DIR/helm-x86_64-pc-windows-msvc.exe"
|
||||
rm -rf "$TMPDIR"
|
||||
|
||||
# Make binaries executable
|
||||
chmod +x "$BINARIES_DIR"/helm-*-linux-* "$BINARIES_DIR"/helm-*-darwin
|
||||
|
||||
echo "helm binaries downloaded successfully to $BINARIES_DIR"
|
||||
echo "Total size:"
|
||||
du -sh "$BINARIES_DIR"
|
||||
@ -330,6 +330,7 @@ pub async fn initiate_oauth(
|
||||
let port_forwards = app_state.port_forwards.clone();
|
||||
let refresh_registry = app_state.refresh_registry.clone();
|
||||
let watchers = app_state.watchers.clone();
|
||||
let log_streams = app_state.log_streams.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let app_state_for_callback = AppState {
|
||||
@ -343,6 +344,7 @@ pub async fn initiate_oauth(
|
||||
port_forwards,
|
||||
refresh_registry,
|
||||
watchers,
|
||||
log_streams,
|
||||
};
|
||||
while let Some(callback) = callback_rx.recv().await {
|
||||
tracing::info!("Received OAuth callback for state: {}", callback.state);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -45,6 +45,7 @@ pub fn run() {
|
||||
port_forwards: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
|
||||
refresh_registry: Arc::new(tokio::sync::Mutex::new(crate::kube::RefreshRegistry::new())),
|
||||
watchers: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
log_streams: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
|
||||
};
|
||||
let stronghold_salt = format!(
|
||||
"tftsr-stronghold-salt-v1-{:x}",
|
||||
@ -232,6 +233,46 @@ pub fn run() {
|
||||
commands::kube::rollback_deployment,
|
||||
commands::kube::create_resource,
|
||||
commands::kube::edit_resource,
|
||||
// Phase 4: Additional Resource Discovery
|
||||
commands::kube::list_replicationcontrollers,
|
||||
commands::kube::list_poddisruptionbudgets,
|
||||
commands::kube::list_priorityclasses,
|
||||
commands::kube::list_runtimeclasses,
|
||||
commands::kube::list_leases,
|
||||
commands::kube::list_mutatingwebhookconfigurations,
|
||||
commands::kube::list_validatingwebhookconfigurations,
|
||||
commands::kube::list_endpoints,
|
||||
commands::kube::list_endpointslices,
|
||||
commands::kube::list_ingressclasses,
|
||||
commands::kube::list_namespaces_resource,
|
||||
commands::kube::list_crds,
|
||||
commands::kube::list_custom_resources,
|
||||
// Phase 5: Action Commands
|
||||
commands::kube::force_delete_resource,
|
||||
commands::kube::describe_resource,
|
||||
commands::kube::get_resource_yaml,
|
||||
commands::kube::attach_pod,
|
||||
commands::kube::restart_statefulset,
|
||||
commands::kube::restart_daemonset,
|
||||
commands::kube::scale_statefulset,
|
||||
commands::kube::scale_replicaset,
|
||||
commands::kube::scale_replicationcontroller,
|
||||
commands::kube::suspend_cronjob,
|
||||
commands::kube::resume_cronjob,
|
||||
commands::kube::trigger_cronjob,
|
||||
commands::kube::create_namespace,
|
||||
commands::kube::delete_namespace,
|
||||
// Phase 6: Log Streaming
|
||||
commands::kube::stream_pod_logs,
|
||||
commands::kube::stop_log_stream,
|
||||
// Phase 7: Helm Commands
|
||||
commands::kube::helm_list_repos,
|
||||
commands::kube::helm_add_repo,
|
||||
commands::kube::helm_update_repos,
|
||||
commands::kube::helm_search_repo,
|
||||
commands::kube::helm_list_releases,
|
||||
commands::kube::helm_uninstall,
|
||||
commands::kube::helm_rollback,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("Error running Troubleshooting and RCA Assistant application");
|
||||
|
||||
113
src-tauri/src/shell/helm.rs
Normal file
113
src-tauri/src/shell/helm.rs
Normal file
@ -0,0 +1,113 @@
|
||||
// Helm Binary Management
|
||||
//
|
||||
// This module handles:
|
||||
// - Locating the helm binary (bundled or system PATH)
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn locate_helm() -> Result<PathBuf, String> {
|
||||
// Strategy:
|
||||
// 1. Check for bundled sidecar binary (platform-specific)
|
||||
// 2. Fallback to system PATH (which helm)
|
||||
// 3. Check common installation paths
|
||||
|
||||
let exe_suffix = if cfg!(windows) { ".exe" } else { "" };
|
||||
|
||||
// Try current directory (dev mode)
|
||||
let local_helm = PathBuf::from(format!("helm{exe_suffix}"));
|
||||
if local_helm.exists() {
|
||||
return Ok(local_helm);
|
||||
}
|
||||
|
||||
// Check for Tauri sidecar binary (production builds)
|
||||
if let Ok(exe_path) = std::env::current_exe() {
|
||||
if let Some(exe_dir) = exe_path.parent() {
|
||||
let target = std::env::consts::ARCH.to_string()
|
||||
+ "-"
|
||||
+ if cfg!(target_os = "linux") {
|
||||
"unknown-linux-gnu"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"apple-darwin"
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"pc-windows-msvc"
|
||||
} else {
|
||||
"unknown"
|
||||
};
|
||||
|
||||
let sidecar_name = format!("helm-{target}{exe_suffix}");
|
||||
let sidecar_path = exe_dir.join(&sidecar_name);
|
||||
|
||||
if sidecar_path.exists() {
|
||||
return Ok(sidecar_path);
|
||||
}
|
||||
|
||||
// Also check Resources subdirectory (macOS .app bundle)
|
||||
let resources_path = exe_dir.join("Resources").join(&sidecar_name);
|
||||
if resources_path.exists() {
|
||||
return Ok(resources_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check system PATH
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
if let Ok(output) = Command::new("which").arg("helm").output() {
|
||||
if output.status.success() {
|
||||
let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let path = PathBuf::from(path_str);
|
||||
if path.exists() {
|
||||
return Ok(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if let Ok(output) = Command::new("where").arg("helm").output() {
|
||||
if output.status.success() {
|
||||
let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let path = PathBuf::from(path_str);
|
||||
if path.exists() {
|
||||
return Ok(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check common installation paths
|
||||
let common_paths = [
|
||||
"/usr/local/bin/helm",
|
||||
"/usr/bin/helm",
|
||||
"/opt/homebrew/bin/helm",
|
||||
"/snap/bin/helm",
|
||||
];
|
||||
|
||||
for path_str in &common_paths {
|
||||
let path = PathBuf::from(path_str);
|
||||
if path.exists() {
|
||||
return Ok(path);
|
||||
}
|
||||
}
|
||||
|
||||
Err(
|
||||
"helm binary not found. Please install helm or it will be bundled in production builds."
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_locate_helm_finds_binary() {
|
||||
let result = locate_helm();
|
||||
if result.is_ok() {
|
||||
assert!(result.unwrap().exists(), "helm path should exist if found");
|
||||
}
|
||||
// Test passes whether helm is found or not
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
pub mod classifier;
|
||||
pub mod executor;
|
||||
pub mod helm;
|
||||
pub mod kubeconfig;
|
||||
pub mod kubectl;
|
||||
|
||||
@ -8,5 +9,6 @@ mod tests;
|
||||
|
||||
pub use classifier::{ClassificationResult, CommandClassifier, CommandTier};
|
||||
pub use executor::{execute_with_approval, CommandOutput};
|
||||
pub use helm::locate_helm;
|
||||
pub use kubeconfig::{auto_detect_kubeconfig, KubeconfigInfo};
|
||||
pub use kubectl::{execute_kubectl, locate_kubectl};
|
||||
|
||||
@ -99,6 +99,8 @@ pub struct AppState {
|
||||
pub refresh_registry: Arc<TokioMutex<crate::kube::RefreshRegistry>>,
|
||||
/// Resource watchers: unsubscribe_id -> receiver
|
||||
pub watchers: Arc<Mutex<HashMap<String, tokio::sync::mpsc::Receiver<serde_json::Value>>>>,
|
||||
/// Active pod log streaming tasks: stream_id -> abort handle
|
||||
pub log_streams: Arc<TokioMutex<HashMap<String, tokio::task::AbortHandle>>>,
|
||||
}
|
||||
|
||||
/// Determine the application data directory.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user