2026-03-20 15:17:57 +00:00
|
|
|
use crate::error::{Result, ResultExt, SunbeamError};
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2026-03-20 15:17:57 +00:00
|
|
|
use std::collections::HashMap;
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
use std::path::PathBuf;
|
2026-03-20 15:17:57 +00:00
|
|
|
use std::sync::OnceLock;
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Config data model
|
|
|
|
|
// ---------------------------------------------------------------------------
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
|
|
|
|
|
/// Sunbeam configuration stored at ~/.sunbeam.json.
|
2026-03-20 15:17:57 +00:00
|
|
|
///
|
|
|
|
|
/// Supports kubectl-style named contexts. Each context bundles a domain,
|
|
|
|
|
/// kube context, SSH host, and infrastructure directory.
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
|
pub struct SunbeamConfig {
|
2026-03-20 15:17:57 +00:00
|
|
|
/// The active context name. If empty, uses "default".
|
|
|
|
|
#[serde(default, rename = "current-context")]
|
|
|
|
|
pub current_context: String,
|
|
|
|
|
|
|
|
|
|
/// Named contexts.
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
#[serde(default)]
|
2026-03-20 15:17:57 +00:00
|
|
|
pub contexts: HashMap<String, Context>,
|
|
|
|
|
|
|
|
|
|
// --- Legacy fields (migrated on load) ---
|
|
|
|
|
#[serde(default, skip_serializing_if = "String::is_empty")]
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
pub production_host: String,
|
2026-03-20 15:17:57 +00:00
|
|
|
#[serde(default, skip_serializing_if = "String::is_empty")]
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
pub infra_directory: String,
|
2026-03-20 15:17:57 +00:00
|
|
|
#[serde(default, skip_serializing_if = "String::is_empty")]
|
|
|
|
|
pub acme_email: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A named context — everything needed to target a specific environment.
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
|
pub struct Context {
|
|
|
|
|
/// The domain suffix (e.g. "sunbeam.pt", "192.168.105.3.sslip.io").
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
#[serde(default)]
|
2026-03-20 15:17:57 +00:00
|
|
|
pub domain: String,
|
|
|
|
|
|
|
|
|
|
/// Kubernetes context name (e.g. "production", "sunbeam").
|
|
|
|
|
#[serde(default, rename = "kube-context")]
|
|
|
|
|
pub kube_context: String,
|
|
|
|
|
|
|
|
|
|
/// SSH host for production tunnel (e.g. "sienna@62.210.145.138").
|
|
|
|
|
#[serde(default, rename = "ssh-host")]
|
|
|
|
|
pub ssh_host: String,
|
|
|
|
|
|
|
|
|
|
/// Infrastructure directory root.
|
|
|
|
|
#[serde(default, rename = "infra-dir")]
|
|
|
|
|
pub infra_dir: String,
|
|
|
|
|
|
|
|
|
|
/// ACME email for cert-manager.
|
|
|
|
|
#[serde(default, rename = "acme-email")]
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
pub acme_email: String,
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 15:17:57 +00:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Active context (set once at startup, read everywhere)
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
static ACTIVE_CONTEXT: OnceLock<Context> = OnceLock::new();
|
|
|
|
|
|
|
|
|
|
/// Initialize the active context. Called once from cli::dispatch().
|
|
|
|
|
pub fn set_active_context(ctx: Context) {
|
|
|
|
|
let _ = ACTIVE_CONTEXT.set(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the active context. Panics if not initialized (should never happen
|
|
|
|
|
/// after dispatch starts).
|
|
|
|
|
pub fn active_context() -> &'static Context {
|
|
|
|
|
ACTIVE_CONTEXT.get().expect("active context not initialized")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the domain from the active context. Returns empty string if not set.
|
|
|
|
|
pub fn domain() -> &'static str {
|
|
|
|
|
ACTIVE_CONTEXT
|
|
|
|
|
.get()
|
|
|
|
|
.map(|c| c.domain.as_str())
|
|
|
|
|
.unwrap_or("")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Config file I/O
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
fn config_path() -> PathBuf {
|
|
|
|
|
dirs::home_dir()
|
|
|
|
|
.unwrap_or_else(|| PathBuf::from("."))
|
|
|
|
|
.join(".sunbeam.json")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Load configuration from ~/.sunbeam.json, return default if not found.
|
2026-03-20 15:17:57 +00:00
|
|
|
/// Migrates legacy flat config to context-based format.
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
pub fn load_config() -> SunbeamConfig {
|
|
|
|
|
let path = config_path();
|
|
|
|
|
if !path.exists() {
|
|
|
|
|
return SunbeamConfig::default();
|
|
|
|
|
}
|
2026-03-20 15:17:57 +00:00
|
|
|
let mut config: SunbeamConfig = match std::fs::read_to_string(&path) {
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
Ok(content) => serde_json::from_str(&content).unwrap_or_else(|e| {
|
|
|
|
|
crate::output::warn(&format!(
|
|
|
|
|
"Failed to parse config from {}: {e}",
|
|
|
|
|
path.display()
|
|
|
|
|
));
|
|
|
|
|
SunbeamConfig::default()
|
|
|
|
|
}),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
crate::output::warn(&format!(
|
|
|
|
|
"Failed to read config from {}: {e}",
|
|
|
|
|
path.display()
|
|
|
|
|
));
|
|
|
|
|
SunbeamConfig::default()
|
|
|
|
|
}
|
2026-03-20 15:17:57 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Migrate legacy flat fields into a "production" context
|
|
|
|
|
if !config.production_host.is_empty() && !config.contexts.contains_key("production") {
|
|
|
|
|
let domain = derive_domain_from_host(&config.production_host);
|
|
|
|
|
config.contexts.insert(
|
|
|
|
|
"production".to_string(),
|
|
|
|
|
Context {
|
|
|
|
|
domain,
|
|
|
|
|
kube_context: "production".to_string(),
|
|
|
|
|
ssh_host: config.production_host.clone(),
|
|
|
|
|
infra_dir: config.infra_directory.clone(),
|
|
|
|
|
acme_email: config.acme_email.clone(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
if config.current_context.is_empty() {
|
|
|
|
|
config.current_context = "production".to_string();
|
|
|
|
|
}
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
}
|
2026-03-20 15:17:57 +00:00
|
|
|
|
|
|
|
|
config
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Save configuration to ~/.sunbeam.json.
|
|
|
|
|
pub fn save_config(config: &SunbeamConfig) -> Result<()> {
|
|
|
|
|
let path = config_path();
|
|
|
|
|
if let Some(parent) = path.parent() {
|
2026-03-20 13:15:45 +00:00
|
|
|
std::fs::create_dir_all(parent).with_ctx(|| {
|
2026-03-20 15:17:57 +00:00
|
|
|
format!("Failed to create config directory: {}", parent.display())
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
})?;
|
|
|
|
|
}
|
|
|
|
|
let content = serde_json::to_string_pretty(config)?;
|
|
|
|
|
std::fs::write(&path, content)
|
2026-03-20 13:15:45 +00:00
|
|
|
.with_ctx(|| format!("Failed to save config to {}", path.display()))?;
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
crate::output::ok(&format!("Configuration saved to {}", path.display()));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 15:17:57 +00:00
|
|
|
/// Resolve the context to use, given CLI flags and config.
|
|
|
|
|
pub fn resolve_context(
|
|
|
|
|
config: &SunbeamConfig,
|
|
|
|
|
env_flag: &str,
|
|
|
|
|
context_override: Option<&str>,
|
|
|
|
|
domain_override: &str,
|
|
|
|
|
) -> Context {
|
|
|
|
|
// Start from the named context (CLI --env or current-context)
|
|
|
|
|
let context_name = context_override
|
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
|
if env_flag == "production" {
|
|
|
|
|
"production".to_string()
|
|
|
|
|
} else if env_flag == "local" {
|
|
|
|
|
"local".to_string()
|
|
|
|
|
} else if !config.current_context.is_empty() {
|
|
|
|
|
config.current_context.clone()
|
|
|
|
|
} else {
|
|
|
|
|
"local".to_string()
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let mut ctx = config
|
|
|
|
|
.contexts
|
|
|
|
|
.get(&context_name)
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
|
// Synthesize defaults for well-known names
|
|
|
|
|
match context_name.as_str() {
|
|
|
|
|
"local" => Context {
|
|
|
|
|
kube_context: "sunbeam".to_string(),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
"production" => Context {
|
|
|
|
|
kube_context: "production".to_string(),
|
|
|
|
|
ssh_host: config.production_host.clone(),
|
|
|
|
|
infra_dir: config.infra_directory.clone(),
|
|
|
|
|
acme_email: config.acme_email.clone(),
|
|
|
|
|
domain: derive_domain_from_host(&config.production_host),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
_ => Default::default(),
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// CLI flags override context values
|
|
|
|
|
if !domain_override.is_empty() {
|
|
|
|
|
ctx.domain = domain_override.to_string();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Helpers
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
/// Derive a domain from an SSH host (e.g. "user@admin.sunbeam.pt" → "sunbeam.pt").
|
|
|
|
|
fn derive_domain_from_host(host: &str) -> String {
|
|
|
|
|
let raw = host.split('@').last().unwrap_or(host);
|
|
|
|
|
let raw = raw.split(':').next().unwrap_or(raw);
|
|
|
|
|
let parts: Vec<&str> = raw.split('.').collect();
|
|
|
|
|
if parts.len() >= 2 {
|
|
|
|
|
format!("{}.{}", parts[parts.len() - 2], parts[parts.len() - 1])
|
|
|
|
|
} else {
|
|
|
|
|
String::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
/// Get production host from config or SUNBEAM_SSH_HOST environment variable.
|
|
|
|
|
pub fn get_production_host() -> String {
|
|
|
|
|
let config = load_config();
|
2026-03-20 15:17:57 +00:00
|
|
|
// Check active context first
|
|
|
|
|
if let Some(ctx) = ACTIVE_CONTEXT.get() {
|
|
|
|
|
if !ctx.ssh_host.is_empty() {
|
|
|
|
|
return ctx.ssh_host.clone();
|
|
|
|
|
}
|
|
|
|
|
}
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
if !config.production_host.is_empty() {
|
|
|
|
|
return config.production_host;
|
|
|
|
|
}
|
|
|
|
|
std::env::var("SUNBEAM_SSH_HOST").unwrap_or_default()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Infrastructure manifests directory as a Path.
|
|
|
|
|
pub fn get_infra_dir() -> PathBuf {
|
2026-03-20 15:17:57 +00:00
|
|
|
// Check active context
|
|
|
|
|
if let Some(ctx) = ACTIVE_CONTEXT.get() {
|
|
|
|
|
if !ctx.infra_dir.is_empty() {
|
|
|
|
|
return PathBuf::from(&ctx.infra_dir);
|
|
|
|
|
}
|
|
|
|
|
}
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
let configured = load_config().infra_directory;
|
|
|
|
|
if !configured.is_empty() {
|
|
|
|
|
return PathBuf::from(configured);
|
|
|
|
|
}
|
2026-03-20 15:17:57 +00:00
|
|
|
// Dev fallback
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
std::env::current_exe()
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|p| p.canonicalize().ok())
|
|
|
|
|
.and_then(|p| {
|
|
|
|
|
let mut dir = p.as_path();
|
|
|
|
|
for _ in 0..10 {
|
|
|
|
|
dir = dir.parent()?;
|
|
|
|
|
if dir.join("infrastructure").is_dir() {
|
|
|
|
|
return Some(dir.join("infrastructure"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_else(|| PathBuf::from("infrastructure"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Monorepo root directory (parent of the infrastructure directory).
|
|
|
|
|
pub fn get_repo_root() -> PathBuf {
|
|
|
|
|
get_infra_dir()
|
|
|
|
|
.parent()
|
|
|
|
|
.map(|p| p.to_path_buf())
|
|
|
|
|
.unwrap_or_else(|| PathBuf::from("."))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Clear configuration file.
|
|
|
|
|
pub fn clear_config() -> Result<()> {
|
|
|
|
|
let path = config_path();
|
|
|
|
|
if path.exists() {
|
|
|
|
|
std::fs::remove_file(&path)
|
2026-03-20 13:15:45 +00:00
|
|
|
.with_ctx(|| format!("Failed to remove {}", path.display()))?;
|
2026-03-20 15:17:57 +00:00
|
|
|
crate::output::ok(&format!("Configuration cleared from {}", path.display()));
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
} else {
|
|
|
|
|
crate::output::warn("No configuration file found to clear");
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_default_config() {
|
|
|
|
|
let config = SunbeamConfig::default();
|
2026-03-20 15:17:57 +00:00
|
|
|
assert!(config.current_context.is_empty());
|
|
|
|
|
assert!(config.contexts.is_empty());
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-03-20 15:17:57 +00:00
|
|
|
fn test_derive_domain_from_host() {
|
|
|
|
|
assert_eq!(derive_domain_from_host("sienna@admin.sunbeam.pt"), "sunbeam.pt");
|
|
|
|
|
assert_eq!(derive_domain_from_host("user@62.210.145.138"), "145.138");
|
|
|
|
|
assert_eq!(derive_domain_from_host("sunbeam.pt"), "sunbeam.pt");
|
|
|
|
|
assert_eq!(derive_domain_from_host("localhost"), "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_legacy_migration() {
|
|
|
|
|
let json = r#"{
|
|
|
|
|
"production_host": "sienna@62.210.145.138",
|
|
|
|
|
"infra_directory": "/path/to/infra",
|
|
|
|
|
"acme_email": "ops@sunbeam.pt"
|
|
|
|
|
}"#;
|
|
|
|
|
let config: SunbeamConfig = serde_json::from_str(json).unwrap();
|
|
|
|
|
// After load_config migration, contexts would be populated.
|
|
|
|
|
// Here we just test the struct deserializes legacy fields.
|
|
|
|
|
assert_eq!(config.production_host, "sienna@62.210.145.138");
|
|
|
|
|
assert!(config.contexts.is_empty()); // migration happens in load_config()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_context_roundtrip() {
|
|
|
|
|
let mut config = SunbeamConfig::default();
|
|
|
|
|
config.current_context = "production".to_string();
|
|
|
|
|
config.contexts.insert(
|
|
|
|
|
"production".to_string(),
|
|
|
|
|
Context {
|
|
|
|
|
domain: "sunbeam.pt".to_string(),
|
|
|
|
|
kube_context: "production".to_string(),
|
|
|
|
|
ssh_host: "sienna@server.sunbeam.pt".to_string(),
|
|
|
|
|
infra_dir: "/home/infra".to_string(),
|
|
|
|
|
acme_email: "ops@sunbeam.pt".to_string(),
|
|
|
|
|
},
|
|
|
|
|
);
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
let json = serde_json::to_string(&config).unwrap();
|
|
|
|
|
let loaded: SunbeamConfig = serde_json::from_str(&json).unwrap();
|
2026-03-20 15:17:57 +00:00
|
|
|
assert_eq!(loaded.current_context, "production");
|
|
|
|
|
let ctx = loaded.contexts.get("production").unwrap();
|
|
|
|
|
assert_eq!(ctx.domain, "sunbeam.pt");
|
|
|
|
|
assert_eq!(ctx.ssh_host, "sienna@server.sunbeam.pt");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_resolve_context_from_env_flag() {
|
|
|
|
|
let mut config = SunbeamConfig::default();
|
|
|
|
|
config.contexts.insert(
|
|
|
|
|
"production".to_string(),
|
|
|
|
|
Context {
|
|
|
|
|
domain: "sunbeam.pt".to_string(),
|
|
|
|
|
kube_context: "production".to_string(),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
let ctx = resolve_context(&config, "production", None, "");
|
|
|
|
|
assert_eq!(ctx.domain, "sunbeam.pt");
|
|
|
|
|
assert_eq!(ctx.kube_context, "production");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_resolve_context_domain_override() {
|
|
|
|
|
let config = SunbeamConfig::default();
|
|
|
|
|
let ctx = resolve_context(&config, "local", None, "custom.example.com");
|
|
|
|
|
assert_eq!(ctx.domain, "custom.example.com");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_resolve_context_defaults_local() {
|
|
|
|
|
let config = SunbeamConfig::default();
|
|
|
|
|
let ctx = resolve_context(&config, "local", None, "");
|
|
|
|
|
assert_eq!(ctx.kube_context, "sunbeam");
|
feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:
- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)
23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
|
|
|
}
|
|
|
|
|
}
|