Skip to content
Open
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
36 changes: 36 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions voxel-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,12 @@ pub struct Falcon {
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct SpCfg {
/// Path to the sp-emu binary (illumos) that runs the fleet on the falcon
/// host. Required for --emu.
/// sp-emu binary override for the fleet on the falcon host. Unset, launch
/// takes sp-emu from PATH, else fetches voxel's pinned buildomat build
/// into ~/.cache/voxel.
pub emu_bin: Option<String>,
/// Path to the faux-mgs binary. Optional; the operator `sp` commands need
/// it, launch itself does not.
/// faux-mgs binary override; PATH, then the pinned build, like sp-emu.
/// The operator `sp` commands need it, launch itself does not.
pub faux_mgs: Option<String>,
}

Expand Down
8 changes: 8 additions & 0 deletions voxel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ serde_json = "1"
# Unpack TUF control-plane composites (GNU tar format; illumos tar rejects it).
tar = "0.4"
flate2 = "1"
# Read TUF repo zips in process (no unzip on the host). tufaceous writes
# members stored or deflated; flate2's default backend (pulled in above)
# serves the deflate path.
zip = { version = "4.6", default-features = false, features = ["deflate-flate2"] }
reqwest = { version = "0.13", default-features = false, features = ["rustls"] }
# Fetch prebuilt binaries (sp-emu, faux-mgs) from buildomat: sha256 verified
# against the published digest, progress bar while streaming.
sha2 = "0.10"
indicatif = "0.18"
libc = "0.2"

# Sprockets/trust-quorum test keys - generated at launch (replaces a4x2's
Expand Down
2 changes: 1 addition & 1 deletion voxel/src/cpbuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ fn stage_gz_from_phase2(

const OMICRON_RAW_URL: &str =
"https://raw.githubusercontent.com/oxidecomputer/omicron";
const BUILDOMAT_URL: &str =
pub(crate) const BUILDOMAT_URL: &str =
"https://buildomat.eng.oxide.computer/public/file/oxidecomputer";

fn raw_cache(voxel_image: &Utf8Path, sha: &str) -> Utf8PathBuf {
Expand Down
56 changes: 44 additions & 12 deletions voxel/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ mod wicket_setup;
)]
struct Cli {
/// voxel.toml to use (default: ~/.config/voxel/voxel.toml, then /etc/voxel/voxel.toml).
#[arg(long, global = true, env = "VOXEL_CONFIG")]
#[arg(long, global = true, env = "VOXEL_CONFIG", value_parser = abs_path)]
config: Option<Utf8PathBuf>,

/// Project root that cargo-bay/ and .falcon/ live under.
#[arg(long, global = true, env = "VOXEL_WORKDIR")]
#[arg(long, global = true, env = "VOXEL_WORKDIR", value_parser = abs_path)]
workdir: Option<Utf8PathBuf>,

/// Topology (falcon deployment) name.
Expand All @@ -70,7 +70,7 @@ struct Cli {
dataset: Option<String>,

/// Build root for `image create` (default: `$HOME/voxel-builds`).
#[arg(long, global = true)]
#[arg(long, global = true, value_parser = abs_path)]
build_root: Option<Utf8PathBuf>,

#[command(subcommand)]
Expand Down Expand Up @@ -100,14 +100,15 @@ enum Cmd {
///
/// For trying a hubris build before it ships. The rack then reports a
/// release it is not running, so say so wherever that is claimed.
#[arg(long, value_name = "DIR")]
#[arg(long, value_name = "DIR", value_parser = abs_path)]
sp_firmware: Option<Utf8PathBuf>,
},
/// (debug) Print the wicketd RSS config body that `--wicket-setup` would PUT,
/// reshaped from a generated config-rss.toml (validates the mapping offline).
#[command(hide = true)]
WicketDryrun {
/// Path to a generated config-rss.toml.
#[arg(value_parser = abs_path)]
config_rss: Utf8PathBuf,
/// Per-rack sled count (the bootstrap slot set).
#[arg(default_value_t = 4)]
Expand Down Expand Up @@ -186,7 +187,12 @@ enum Cmd {
reference: Option<String>,

/// Use an existing Omicron checkout without fetching or changing it.
#[arg(long, value_name = "PATH", conflicts_with = "reference")]
#[arg(
long,
value_name = "PATH",
conflicts_with = "reference",
value_parser = abs_path
)]
source: Option<Utf8PathBuf>,

/// Rack to target (1-based).
Expand Down Expand Up @@ -226,7 +232,10 @@ enum ConfigCmd {
/// Set a dotted scalar key, e.g. `topology.sleds 3`.
Set { key: String, value: String },
/// Validate and install a prepared voxel.toml.
Load { file: Utf8PathBuf },
Load {
#[arg(value_parser = abs_path)]
file: Utf8PathBuf,
},
}

#[derive(Subcommand)]
Expand All @@ -246,18 +255,23 @@ enum ImageCmd {
commit: Option<String>,
/// Build from an existing omicron checkout/worktree AS-IS (host build,
/// for dev): skips clone + checkout so your working-tree edits are built.
#[arg(long)]
#[arg(long, value_parser = abs_path)]
src: Option<Utf8PathBuf>,
/// Build the image from this TUF repo's artifacts with no omicron
/// compile: zones + corpus byte exact, GZ software from the host OS
/// phase 2 payload, switch zone recomposed for softnpu.
#[arg(long, value_name = "REPO_ZIP")]
#[arg(long, value_name = "REPO_ZIP", value_parser = abs_path)]
from_tuf: Option<Utf8PathBuf>,
/// With --from-tuf: an omicron-sled-agent package tar built with
/// switch-softnpu, staged in place of the phase 2 sled-agent. The
/// standard-image binary hardwires scrimlet = tofino ASIC, so softnpu
/// scrimlets need this build.
#[arg(long, value_name = "PKG_TAR", requires = "from_tuf")]
#[arg(
long,
value_name = "PKG_TAR",
requires = "from_tuf",
value_parser = abs_path
)]
sled_agent: Option<Utf8PathBuf>,
},
/// Export an image bundle to a file for distribution.
Expand All @@ -268,6 +282,7 @@ enum ImageCmd {
/// Image name (e.g. `voxel-cp-a3fee0ec`).
name: String,
/// Output file (default `<name>.zfs.zst`, or `<name>.raw.xz` with --raw).
#[arg(value_parser = abs_path)]
out: Option<Utf8PathBuf>,
/// Portable raw disk image (`dd | xz`) instead of a zfs stream.
#[arg(long)]
Expand All @@ -276,6 +291,7 @@ enum ImageCmd {
/// Import an image bundle (`.zfs.zst` or `.raw.xz`) from `image export`.
Import {
/// File to import (name is derived from it).
#[arg(value_parser = abs_path)]
file: Utf8PathBuf,
},
/// Remove an image bundle (`zfs destroy <dataset>/img/<name>`).
Expand Down Expand Up @@ -348,6 +364,7 @@ enum ImageCmd {
#[command(hide = true)]
RenderSmf {
/// Path to the omicron checkout root.
#[arg(value_parser = abs_path)]
omicron_root: Utf8PathBuf,
/// Number of gimlet SPs to simulate (sp-sim).
#[arg(long, default_value_t = 4)]
Expand Down Expand Up @@ -487,8 +504,10 @@ enum SpCmd {
/// Flash a hubris `.zip` into an sp-emu slot-A flash file (offline).
Flash {
/// Hubris image archive (e.g. build-gimlet-c-image-default.zip).
#[arg(value_parser = abs_path)]
image: Utf8PathBuf,
/// Output flash file.
#[arg(value_parser = abs_path)]
out: Utf8PathBuf,
},
/// Re-flash a live SP (or the shared RoT) and restart its sp-emu service.
Expand All @@ -500,6 +519,7 @@ enum SpCmd {
/// Target: `sidecar` | `gN` | a port | `rot`.
target: String,
/// Hubris `.zip` (SP) or raw oxide-rot-1 flash image (target `rot`).
#[arg(value_parser = abs_path)]
image: Utf8PathBuf,
#[arg(long, default_value = "switch0")]
switch: String,
Expand Down Expand Up @@ -603,6 +623,7 @@ enum RepoCmd {
/// waiting out TUF replication. Run after the repo upload.
Seed {
/// The TUF repo zip that was uploaded.
#[arg(value_parser = abs_path)]
repo: Utf8PathBuf,
},
}
Expand All @@ -628,17 +649,28 @@ fn load_config(path: &Utf8Path) -> anyhow::Result<VoxelConfig> {
Ok(cfg)
}

/// Make a path absolute against the current directory.
/// clap parser for path arguments. main chdirs to the workdir before
/// dispatching, so relative paths must be resolved while parsing, against
/// the directory voxel was invoked from.
fn abs_path(s: &str) -> Result<Utf8PathBuf, String> {
Ok(absolutize(Utf8PathBuf::from(s)))
}

/// Make a path absolute against the current directory, dropping `.`
/// components so `./x` reads as `<cwd>/x`.
fn absolutize(p: Utf8PathBuf) -> Utf8PathBuf {
if p.is_absolute() {
let abs = if p.is_absolute() {
p
} else {
let cwd = std::env::current_dir()
.ok()
.and_then(|d| Utf8PathBuf::try_from(d).ok())
.unwrap_or_default();
cwd.join(p)
}
};
abs.components()
.filter(|c| !matches!(c, camino::Utf8Component::CurDir))
.collect()
}

/// Discover the `voxel.toml` to use, as an absolute path. Order: explicit
Expand Down
22 changes: 4 additions & 18 deletions voxel/src/repocmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,30 +215,16 @@ fn discover_stores(name: &str, ip: &str) -> Result<Sled> {
Ok(Sled { name: name.to_string(), ip: ip.to_string(), pools, have })
}

/// Stream one zip member into the staging dir under its sha name,
/// returning its size.
/// Copy one repo member into the staging dir under its sha name, returning
/// its size.
fn stage_member(
repo: &Utf8Path,
member: &str,
staging: &Utf8Path,
sha: &str,
) -> Result<u64> {
let mut unzip = Command::new("unzip")
.args(["-p", repo.as_str(), member])
.stdout(Stdio::piped())
.spawn()
.context("spawn unzip -p")?;
let mut out = unzip.stdout.take().context("unzip stdout")?;
let dest = staging.join(sha);
let mut file =
fs::File::create(&dest).with_context(|| format!("create {dest}"))?;
let n = std::io::copy(&mut out, &mut file)
.with_context(|| format!("stage {member}"))?;
let status = unzip.wait().context("wait for unzip")?;
if !status.success() {
bail!("unzip -p {member} exited with {status}");
}
Ok(n)
crate::tufrepo::extract_from(repo, member, &staging.join(sha))
.with_context(|| format!("stage {member}"))
}

/// An ssh command with voxel's usual empty-root-password access.
Expand Down
Loading