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.
128 lines
3.7 KiB
Rust
128 lines
3.7 KiB
Rust
use flate2::read::GzDecoder;
|
|
use std::env;
|
|
use std::fs;
|
|
use std::io::Read;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
use tar::Archive;
|
|
|
|
const KUSTOMIZE_VERSION: &str = "v5.8.1";
|
|
const HELM_VERSION: &str = "v4.1.0";
|
|
|
|
fn main() {
|
|
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
let target = env::var("TARGET").unwrap_or_default();
|
|
let (os, arch) = parse_target(&target);
|
|
|
|
download_and_embed("kustomize", KUSTOMIZE_VERSION, &os, &arch, &out_dir);
|
|
download_and_embed("helm", HELM_VERSION, &os, &arch, &out_dir);
|
|
|
|
// Set version info from git
|
|
let commit = git_commit_sha();
|
|
println!("cargo:rustc-env=SUNBEAM_COMMIT={commit}");
|
|
|
|
// Rebuild if git HEAD changes
|
|
println!("cargo:rerun-if-changed=.git/HEAD");
|
|
}
|
|
|
|
fn parse_target(target: &str) -> (String, String) {
|
|
let os = if target.contains("darwin") {
|
|
"darwin"
|
|
} else if target.contains("linux") {
|
|
"linux"
|
|
} else if cfg!(target_os = "macos") {
|
|
"darwin"
|
|
} else {
|
|
"linux"
|
|
};
|
|
|
|
let arch = if target.contains("aarch64") || target.contains("arm64") {
|
|
"arm64"
|
|
} else if target.contains("x86_64") || target.contains("amd64") {
|
|
"amd64"
|
|
} else if cfg!(target_arch = "aarch64") {
|
|
"arm64"
|
|
} else {
|
|
"amd64"
|
|
};
|
|
|
|
(os.to_string(), arch.to_string())
|
|
}
|
|
|
|
fn download_and_embed(tool: &str, version: &str, os: &str, arch: &str, out_dir: &PathBuf) {
|
|
let dest = out_dir.join(tool);
|
|
if dest.exists() {
|
|
return;
|
|
}
|
|
|
|
let url = match tool {
|
|
"kustomize" => format!(
|
|
"https://github.com/kubernetes-sigs/kustomize/releases/download/\
|
|
kustomize%2F{version}/kustomize_{version}_{os}_{arch}.tar.gz"
|
|
),
|
|
"helm" => format!(
|
|
"https://get.helm.sh/helm-{version}-{os}-{arch}.tar.gz"
|
|
),
|
|
_ => panic!("Unknown tool: {tool}"),
|
|
};
|
|
|
|
let extract_path = match tool {
|
|
"kustomize" => "kustomize".to_string(),
|
|
"helm" => format!("{os}-{arch}/helm"),
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
eprintln!("cargo:warning=Downloading {tool} {version} for {os}/{arch}...");
|
|
|
|
let response = reqwest::blocking::get(&url)
|
|
.unwrap_or_else(|e| panic!("Failed to download {tool}: {e}"));
|
|
let bytes = response
|
|
.bytes()
|
|
.unwrap_or_else(|e| panic!("Failed to read {tool} response: {e}"));
|
|
|
|
let decoder = GzDecoder::new(&bytes[..]);
|
|
let mut archive = Archive::new(decoder);
|
|
|
|
for entry in archive.entries().expect("Failed to read tar entries") {
|
|
let mut entry = entry.expect("Failed to read tar entry");
|
|
let path = entry
|
|
.path()
|
|
.expect("Failed to read entry path")
|
|
.to_path_buf();
|
|
if path.to_string_lossy() == extract_path {
|
|
let mut data = Vec::new();
|
|
entry
|
|
.read_to_end(&mut data)
|
|
.expect("Failed to read binary");
|
|
fs::write(&dest, &data).expect("Failed to write binary");
|
|
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::PermissionsExt;
|
|
fs::set_permissions(&dest, fs::Permissions::from_mode(0o755))
|
|
.expect("Failed to set permissions");
|
|
}
|
|
|
|
eprintln!("cargo:warning=Embedded {tool} ({} bytes)", data.len());
|
|
return;
|
|
}
|
|
}
|
|
|
|
panic!("Could not find {extract_path} in {tool} archive");
|
|
}
|
|
|
|
fn git_commit_sha() -> String {
|
|
Command::new("git")
|
|
.args(["rev-parse", "--short=8", "HEAD"])
|
|
.output()
|
|
.ok()
|
|
.and_then(|o| {
|
|
if o.status.success() {
|
|
Some(String::from_utf8_lossy(&o.stdout).trim().to_string())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.unwrap_or_else(|| "unknown".to_string())
|
|
}
|