Files
harmony/fleet/harmony-fleet-deploy/src/main.rs
Jean-Gabriel Gill-Couture a4b3d18bd6 refactor(fleet): drop deploy-crate dev creds, HARMONY_* env vars, lean docs
Caller must pass `UserPassCredentials` to `FleetNatsScore::user_pass`
— no more `e2e-admin`/`e2e-device` defaults shipped in the library.
The deploy binary reads `HARMONY_FLEET_*` env vars (default namespace
`harmony-fleet-system`) and fails fast when NATS creds aren't set.

Also: `style/dist/` gitignored, `manual_mint/mint.py` moved next to
`nats/callout/` with README + secrets gitignore (the real RSA key
that was sitting untracked has been removed), `architecture_review.md`
moved to `docs/adr/drafts/024-`, three low-value ROADMAP docs deleted.

Updates pre-merge checklist (§1.6, §1.8, §3.1, §5).
2026-05-22 17:54:48 -04:00

154 lines
5.3 KiB
Rust

//! `harmony-fleet-deploy` — deploy the fleet stack to a cluster.
//!
//! Built on `harmony_cli::run` like the rest of the workspace's
//! deploy binaries (`harmony_agent_deploy`, …). The CLI offers a
//! minimal env-driven config and hands off to `harmony_cli`, which
//! provides the standard `--filter` / `--all` / `--list` selection
//! surface; pick a single component by filter or run them all.
//!
//! Topology default is [`K8sAnywhereTopology::from_env`] — local k3d
//! when `HARMONY_USE_LOCAL_K3D` flips, otherwise whatever cluster
//! `KUBECONFIG` points at. Per ADR-023 the binary's full set of
//! topologies is compile-time; for the moment only `K8sAnywhere` is
//! wired in, with `K8sBareTopology` planned for the next iteration.
//!
//! What the binary owns: assembling the Scores from environment
//! input. What it does **not** own: any handrolled k8s manifests,
//! any imperative bring-up loops, any auth secret rendering — that
//! all sits inside the `*Score` impls in [`harmony_fleet_deploy`].
use anyhow::{Context, Result};
use clap::Parser;
use harmony::inventory::Inventory;
use harmony::score::Score;
use harmony::topology::K8sAnywhereTopology;
use harmony_fleet_deploy::nats::UserPassCredentials;
use harmony_fleet_deploy::{FleetAgentScore, FleetNatsScore, FleetOperatorScore, agent::PodTarget};
#[derive(Parser, Debug)]
#[command(
name = "harmony-fleet-deploy",
about = "Deploy the harmony fleet stack to a Kubernetes cluster"
)]
struct CliConfig {
/// Namespace every component lands in. Override with
/// `HARMONY_FLEET_NAMESPACE`.
#[arg(
long,
env = "HARMONY_FLEET_NAMESPACE",
default_value = "harmony-fleet-system"
)]
namespace: String,
/// Operator image to pull. Defaults to the dev tag the
/// `harmony-fleet-operator/Dockerfile` produces.
#[arg(
long,
env = "HARMONY_FLEET_OPERATOR_IMAGE",
default_value = "localhost/harmony-fleet-operator:dev"
)]
operator_image: String,
/// Agent image to pull. The e2e harness sideloads
/// `localhost/harmony-fleet-agent:e2e`; production override via
/// the env var.
#[arg(
long,
env = "HARMONY_FLEET_AGENT_IMAGE",
default_value = "localhost/harmony-fleet-agent:e2e"
)]
agent_image: String,
/// Device id for the agent Pod this deploy provisions. Production
/// will likely deploy multiple agents; for the minimal CLI v1 the
/// caller runs the binary once per device.
#[arg(
long,
env = "HARMONY_FLEET_AGENT_DEVICE_ID",
default_value = "fleet-agent-00"
)]
agent_device_id: String,
/// Host-side NodePort the NATS Service exposes.
#[arg(long, env = "HARMONY_FLEET_NATS_NODE_PORT", default_value_t = 30423)]
nats_node_port: u16,
/// NATS admin user (full pub/sub on the `APP` account). Required.
#[arg(long, env = "HARMONY_FLEET_NATS_ADMIN_USER")]
nats_admin_user: Option<String>,
/// NATS admin password. Required.
#[arg(long, env = "HARMONY_FLEET_NATS_ADMIN_PASS")]
nats_admin_pass: Option<String>,
/// NATS device user (limited pub/sub). Required.
#[arg(long, env = "HARMONY_FLEET_NATS_DEVICE_USER")]
nats_device_user: Option<String>,
/// NATS device password. Required.
#[arg(long, env = "HARMONY_FLEET_NATS_DEVICE_PASS")]
nats_device_pass: Option<String>,
}
impl CliConfig {
/// Build the NATS credentials struct or fail with an actionable
/// error message naming the missing env var. No dev defaults —
/// the deploy binary refuses to ship known-weak passwords.
fn nats_creds(&self) -> Result<UserPassCredentials> {
Ok(UserPassCredentials {
admin_user: self
.nats_admin_user
.clone()
.context("HARMONY_FLEET_NATS_ADMIN_USER must be set")?,
admin_pass: self
.nats_admin_pass
.clone()
.context("HARMONY_FLEET_NATS_ADMIN_PASS must be set")?,
device_user: self
.nats_device_user
.clone()
.context("HARMONY_FLEET_NATS_DEVICE_USER must be set")?,
device_pass: self
.nats_device_pass
.clone()
.context("HARMONY_FLEET_NATS_DEVICE_PASS must be set")?,
})
}
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = CliConfig::parse();
let creds = cli.nats_creds()?;
let device_user = creds.device_user.clone();
let device_pass = creds.device_pass.clone();
let nats = FleetNatsScore::user_pass(cli.namespace.clone(), cli.nats_node_port, creds);
let operator = FleetOperatorScore::new()
.namespace(cli.namespace.clone())
.image(cli.operator_image.clone())
.nats_url(nats.in_cluster_url());
let agent = FleetAgentScore::pod(
cli.namespace.clone(),
PodTarget::user_pass(
cli.agent_device_id.clone(),
cli.agent_image.clone(),
nats.in_cluster_url(),
device_user,
device_pass,
),
);
let scores: Vec<Box<dyn Score<K8sAnywhereTopology>>> =
vec![Box::new(nats), Box::new(operator), Box::new(agent)];
harmony_cli::run(
Inventory::autoload(),
K8sAnywhereTopology::from_env(),
scores,
None,
)
.await
.map_err(|e| anyhow::anyhow!("{e}"))
}