Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/app/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use crate::{
commands::palette::CommandAction,
config::settings::Settings,
domain::{menu::MenuItem, screen::Screen},
project::{ProjectCapabilities, ProjectContext, RuntimeCapabilities},
project::{runtime, ProjectCapabilities, ProjectContext, RuntimeCapabilities},
storage::history::ProjectHistory,
ui::{
menus::CapabilityMenuGenerator,
state::{Notification, UiState},
theme::ThemeName,
},
Expand Down Expand Up @@ -56,6 +57,17 @@ impl AppState {
self.ui
.push_notification(Notification::warning(message.into()));
}

pub fn refresh_runtime(&mut self) {
self.runtime = runtime::detect(&self.capabilities);
self.menus = CapabilityMenuGenerator::generate(&self.capabilities, &self.runtime);
self.actions = crate::commands::palette::generate_actions(
&self.project,
&self.capabilities,
&self.runtime,
);
self.status_message = "Runtime refreshed".to_string();
}
}

#[derive(Debug, Clone)]
Expand Down
14 changes: 14 additions & 0 deletions src/config/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ pub fn doctor_report_file() -> PathBuf {
config_dir().join("doctor_report.json")
}

pub fn command_history_file() -> PathBuf {
config_dir().join("command_history.yaml")
}

pub fn project_cache_file() -> PathBuf {
config_dir().join("project_cache.yaml")
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -38,4 +46,10 @@ mod tests {
let path = doctor_report_file();
assert!(path.to_string_lossy().contains("doctor_report.json"));
}

#[test]
fn command_history_path_is_under_config_dir() {
let path = command_history_file();
assert!(path.to_string_lossy().contains("command_history.yaml"));
}
}
44 changes: 44 additions & 0 deletions src/kubernetes/command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use anyhow::{Context, Result};
use std::process::Command;

pub fn run_kubectl(args: &[&str]) -> Result<String> {
let output = Command::new("kubectl")
.args(args)
.output()
.with_context(|| format!("Failed to run kubectl {}", args.join(" ")))?;

if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("kubectl {} failed: {}", args.join(" "), stderr.trim());
}

Ok(String::from_utf8_lossy(&output.stdout).to_string())
}

pub fn non_empty_lines(output: &str) -> impl Iterator<Item = &str> {
output
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
}

pub fn kubectl_list(resource: &str, namespace: Option<&str>) -> Result<String> {
let mut args = vec!["get", resource, "--no-headers"];
if let Some(namespace) = namespace {
args.push("-n");
args.push(namespace);
}

run_kubectl(&args)
}

#[cfg(test)]
mod tests {
use super::non_empty_lines;

#[test]
fn trims_empty_lines() {
let lines = non_empty_lines(" a \n\n b\n").collect::<Vec<_>>();
assert_eq!(lines, vec!["a", "b"]);
}
}
28 changes: 28 additions & 0 deletions src/kubernetes/configmaps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,31 @@
pub struct ConfigMapSummary {
pub name: String,
}

pub fn list(namespace: &str) -> anyhow::Result<Vec<ConfigMapSummary>> {
let output = crate::kubernetes::command::kubectl_list("configmaps", Some(namespace))?;
Ok(parse_names(&output))
}

pub fn get(name: &str, namespace: &str) -> anyhow::Result<String> {
crate::kubernetes::command::run_kubectl(&[
"get",
"configmap",
name,
"-n",
namespace,
"-o",
"yaml",
])
}

fn parse_names(output: &str) -> Vec<ConfigMapSummary> {
crate::kubernetes::command::non_empty_lines(output)
.filter_map(|line| {
let name = line.split_whitespace().next()?;
Some(ConfigMapSummary {
name: name.to_string(),
})
})
.collect()
}
54 changes: 54 additions & 0 deletions src/kubernetes/deployments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,57 @@ pub struct DeploymentSummary {
pub namespace: String,
pub ready: String,
}

pub fn list(namespace: &str) -> anyhow::Result<Vec<DeploymentSummary>> {
let output = crate::kubernetes::command::kubectl_list("deployments", Some(namespace))?;
Ok(parse_deployments(namespace, &output))
}

pub fn get(name: &str, namespace: &str) -> anyhow::Result<String> {
crate::kubernetes::command::run_kubectl(&[
"get",
"deployment",
name,
"-n",
namespace,
"-o",
"yaml",
])
}

pub fn scale(name: &str, namespace: &str, replicas: u16) -> anyhow::Result<String> {
crate::kubernetes::command::run_kubectl(&[
"scale",
"deployment",
name,
"-n",
namespace,
"--replicas",
&replicas.to_string(),
])
}

fn parse_deployments(namespace: &str, output: &str) -> Vec<DeploymentSummary> {
crate::kubernetes::command::non_empty_lines(output)
.filter_map(|line| {
let columns = line.split_whitespace().collect::<Vec<_>>();
Some(DeploymentSummary {
name: columns.first()?.to_string(),
namespace: namespace.to_string(),
ready: columns.get(1).unwrap_or(&"unknown").to_string(),
})
})
.collect()
}

#[cfg(test)]
mod tests {
use super::parse_deployments;

#[test]
fn parses_deployment_rows() {
let deployments = parse_deployments("default", "web 2/2 2 2 3m\n");
assert_eq!(deployments[0].name, "web");
assert_eq!(deployments[0].ready, "2/2");
}
}
24 changes: 24 additions & 0 deletions src/kubernetes/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,27 @@ pub struct KubernetesEvent {
pub reason: String,
pub message: String,
}

pub fn list(namespace: &str) -> anyhow::Result<Vec<KubernetesEvent>> {
let output = crate::kubernetes::command::run_kubectl(&[
"get",
"events",
"-n",
namespace,
"--no-headers",
])?;
Ok(parse_events(&output))
}

fn parse_events(output: &str) -> Vec<KubernetesEvent> {
crate::kubernetes::command::non_empty_lines(output)
.filter_map(|line| {
let columns = line.split_whitespace().collect::<Vec<_>>();
let reason = columns.get(2).or_else(|| columns.first())?;
Some(KubernetesEvent {
reason: reason.to_string(),
message: line.to_string(),
})
})
.collect()
}
34 changes: 34 additions & 0 deletions src/kubernetes/ingress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,37 @@ pub struct IngressSummary {
pub name: String,
pub host: String,
}

pub fn list(namespace: &str) -> anyhow::Result<Vec<IngressSummary>> {
let output = crate::kubernetes::command::kubectl_list("ingress", Some(namespace))?;
Ok(parse_ingress(&output))
}

pub fn get(name: &str, namespace: &str) -> anyhow::Result<String> {
crate::kubernetes::command::run_kubectl(&[
"get", "ingress", name, "-n", namespace, "-o", "yaml",
])
}

fn parse_ingress(output: &str) -> Vec<IngressSummary> {
crate::kubernetes::command::non_empty_lines(output)
.filter_map(|line| {
let columns = line.split_whitespace().collect::<Vec<_>>();
Some(IngressSummary {
name: columns.first()?.to_string(),
host: columns.get(2).unwrap_or(&"").to_string(),
})
})
.collect()
}

#[cfg(test)]
mod tests {
use super::parse_ingress;

#[test]
fn parses_ingress() {
let ingress = parse_ingress("web nginx app.example.com 1.2.3.4 80 1m\n");
assert_eq!(ingress[0].host, "app.example.com");
}
}
1 change: 1 addition & 0 deletions src/kubernetes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod command;
pub mod configmaps;
pub mod deployments;
pub mod events;
Expand Down
20 changes: 20 additions & 0 deletions src/kubernetes/namespaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,23 @@
pub struct NamespaceSummary {
pub name: String,
}

pub fn list() -> anyhow::Result<Vec<NamespaceSummary>> {
let output = crate::kubernetes::command::kubectl_list("namespaces", None)?;
Ok(parse_namespaces(&output))
}

pub fn get(name: &str) -> anyhow::Result<String> {
crate::kubernetes::command::run_kubectl(&["get", "namespace", name, "-o", "yaml"])
}

fn parse_namespaces(output: &str) -> Vec<NamespaceSummary> {
crate::kubernetes::command::non_empty_lines(output)
.filter_map(|line| {
let name = line.split_whitespace().next()?;
Some(NamespaceSummary {
name: name.to_string(),
})
})
.collect()
}
47 changes: 47 additions & 0 deletions src/kubernetes/pods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,50 @@ pub struct PodSummary {
pub namespace: String,
pub status: String,
}

pub fn list(namespace: &str) -> anyhow::Result<Vec<PodSummary>> {
let output = crate::kubernetes::command::kubectl_list("pods", Some(namespace))?;
Ok(parse_pods(namespace, &output))
}

pub fn get(name: &str, namespace: &str) -> anyhow::Result<String> {
crate::kubernetes::command::run_kubectl(&["get", "pod", name, "-n", namespace, "-o", "yaml"])
}

pub fn delete(name: &str, namespace: &str) -> anyhow::Result<String> {
crate::kubernetes::command::run_kubectl(&["delete", "pod", name, "-n", namespace])
}

pub fn logs(name: &str, namespace: &str, tail: Option<u16>) -> anyhow::Result<Vec<String>> {
let tail = tail.unwrap_or(100).to_string();
let output =
crate::kubernetes::command::run_kubectl(&["logs", name, "-n", namespace, "--tail", &tail])?;
Ok(crate::kubernetes::command::non_empty_lines(&output)
.map(str::to_string)
.collect())
}

fn parse_pods(namespace: &str, output: &str) -> Vec<PodSummary> {
crate::kubernetes::command::non_empty_lines(output)
.filter_map(|line| {
let columns = line.split_whitespace().collect::<Vec<_>>();
Some(PodSummary {
name: columns.first()?.to_string(),
namespace: namespace.to_string(),
status: columns.get(2).unwrap_or(&"Unknown").to_string(),
})
})
.collect()
}

#[cfg(test)]
mod tests {
use super::parse_pods;

#[test]
fn parses_pod_rows() {
let pods = parse_pods("default", "web-abc 1/1 Running 0 1m\n");
assert_eq!(pods[0].name, "web-abc");
assert_eq!(pods[0].status, "Running");
}
}
20 changes: 20 additions & 0 deletions src/kubernetes/secrets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,23 @@
pub struct SecretSummary {
pub name: String,
}

pub fn list(namespace: &str) -> anyhow::Result<Vec<SecretSummary>> {
let output = crate::kubernetes::command::kubectl_list("secrets", Some(namespace))?;
Ok(parse_names(&output))
}

pub fn get(name: &str, namespace: &str) -> anyhow::Result<String> {
crate::kubernetes::command::run_kubectl(&["get", "secret", name, "-n", namespace, "-o", "yaml"])
}

fn parse_names(output: &str) -> Vec<SecretSummary> {
crate::kubernetes::command::non_empty_lines(output)
.filter_map(|line| {
let name = line.split_whitespace().next()?;
Some(SecretSummary {
name: name.to_string(),
})
})
.collect()
}
34 changes: 34 additions & 0 deletions src/kubernetes/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,37 @@ pub struct ServiceSummary {
pub name: String,
pub namespace: String,
}

pub fn list(namespace: &str) -> anyhow::Result<Vec<ServiceSummary>> {
let output = crate::kubernetes::command::kubectl_list("services", Some(namespace))?;
Ok(parse_services(namespace, &output))
}

pub fn get(name: &str, namespace: &str) -> anyhow::Result<String> {
crate::kubernetes::command::run_kubectl(&[
"get", "service", name, "-n", namespace, "-o", "yaml",
])
}

fn parse_services(namespace: &str, output: &str) -> Vec<ServiceSummary> {
crate::kubernetes::command::non_empty_lines(output)
.filter_map(|line| {
let name = line.split_whitespace().next()?;
Some(ServiceSummary {
name: name.to_string(),
namespace: namespace.to_string(),
})
})
.collect()
}

#[cfg(test)]
mod tests {
use super::parse_services;

#[test]
fn parses_services() {
let services = parse_services("default", "web ClusterIP 10.0.0.1 <none> 80/TCP 1m\n");
assert_eq!(services[0].name, "web");
}
}
Loading