feat: first-draft preview-capable zosstorage
- CLI: add topology selection (-t/--topology), preview flags (--show/--report), and removable policy override (--allow-removable) (src/cli/args.rs) - Config: built-in sensible defaults; deterministic overlays for logging, fstab, removable, topology (src/config/loader.rs) - Device: discovery via /proc + /sys with include/exclude regex and removable policy (src/device/discovery.rs) - Idempotency: detection via blkid; safe emptiness checks (src/idempotency/mod.rs) - Partition: topology-driven planning (Single, DualIndependent, BtrfsRaid1, SsdHddBcachefs) (src/partition/plan.rs) - FS: planning + creation (mkfs.vfat, mkfs.btrfs, bcachefs format) and UUID capture via blkid (src/fs/plan.rs) - Orchestrator: pre-flight with preview JSON (disks, partition_plan, filesystems_planned, mount scheme). Skips emptiness in preview; supports stdout+file (src/orchestrator/run.rs) - Util/Logging/Types/Errors: process execution, tracing, shared types (src/util/mod.rs, src/logging/mod.rs, src/types.rs, src/errors.rs) - Docs: add README with exhaustive usage and preview JSON shape (README.md) Builds and unit tests pass: discovery, util, idempotency helpers, and fs parser tests.
This commit is contained in:
758
docs/API-SKELETONS.md
Normal file
758
docs/API-SKELETONS.md
Normal file
@@ -0,0 +1,758 @@
|
||||
# zosstorage API Skeletons (Proposed)
|
||||
|
||||
Purpose
|
||||
- This document proposes concrete Rust module skeletons with public API surface and doc comments.
|
||||
- Bodies intentionally use todo!() or are omitted for approval-first workflow.
|
||||
- After approval, these will be created in the src tree in Code mode.
|
||||
|
||||
Index
|
||||
- [src/lib.rs](src/lib.rs)
|
||||
- [src/errors.rs](src/errors.rs)
|
||||
- [src/main.rs](src/main.rs)
|
||||
- [src/cli/args.rs](src/cli/args.rs)
|
||||
- [src/logging/mod.rs](src/logging/mod.rs)
|
||||
- [src/types.rs](src/types.rs)
|
||||
- [src/config/loader.rs](src/config/loader.rs)
|
||||
- [src/device/discovery.rs](src/device/discovery.rs)
|
||||
- [src/partition/plan.rs](src/partition/plan.rs)
|
||||
- [src/fs/plan.rs](src/fs/plan.rs)
|
||||
- [src/mount/ops.rs](src/mount/ops.rs)
|
||||
- [src/report/state.rs](src/report/state.rs)
|
||||
- [src/orchestrator/run.rs](src/orchestrator/run.rs)
|
||||
- [src/idempotency/mod.rs](src/idempotency/mod.rs)
|
||||
- [src/util/mod.rs](src/util/mod.rs)
|
||||
|
||||
Conventions
|
||||
- Shared [type Result<T>](src/errors.rs:1) and [enum Error](src/errors.rs:1).
|
||||
- No stdout prints; use tracing only.
|
||||
- External tools invoked via [util](src/util/mod.rs) wrappers.
|
||||
|
||||
---
|
||||
|
||||
## Crate root
|
||||
|
||||
References
|
||||
- [src/lib.rs](src/lib.rs)
|
||||
- [type Result<T> = std::result::Result<T, Error>](src/errors.rs:1)
|
||||
|
||||
Skeleton (for later implementation in code mode)
|
||||
```rust
|
||||
//! Crate root for zosstorage: one-shot disk provisioning utility for initramfs.
|
||||
|
||||
pub mod cli;
|
||||
pub mod logging;
|
||||
pub mod config;
|
||||
pub mod device;
|
||||
pub mod partition;
|
||||
pub mod fs;
|
||||
pub mod mount;
|
||||
pub mod report;
|
||||
pub mod orchestrator;
|
||||
pub mod idempotency;
|
||||
pub mod util;
|
||||
pub mod errors;
|
||||
|
||||
pub use errors::{Error, Result};
|
||||
|
||||
/// Crate version constants.
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Errors
|
||||
|
||||
References
|
||||
- [enum Error](src/errors.rs:1)
|
||||
- [type Result<T>](src/errors.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
/// Top-level error for zosstorage.
|
||||
#[derive(Debug, ThisError)]
|
||||
pub enum Error {
|
||||
#[error("configuration error: {0}")]
|
||||
Config(String),
|
||||
#[error("validation error: {0}")]
|
||||
Validation(String),
|
||||
#[error("device discovery error: {0}")]
|
||||
Device(String),
|
||||
#[error("partitioning error: {0}")]
|
||||
Partition(String),
|
||||
#[error("filesystem error: {0}")]
|
||||
Filesystem(String),
|
||||
#[error("mount error: {0}")]
|
||||
Mount(String),
|
||||
#[error("report error: {0}")]
|
||||
Report(String),
|
||||
#[error("external tool '{tool}' failed with status {status}: {stderr}")]
|
||||
Tool {
|
||||
tool: String,
|
||||
status: i32,
|
||||
stderr: String,
|
||||
},
|
||||
#[error("unimplemented: {0}")]
|
||||
Unimplemented(&'static str),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entrypoint
|
||||
|
||||
References
|
||||
- [fn main()](src/main.rs:1)
|
||||
- [fn run(ctx: &Context) -> Result<()>](src/orchestrator/run.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use zosstorage::{cli, config, logging, orchestrator, Result};
|
||||
|
||||
fn main() {
|
||||
// Initialize minimal logging early (fallback).
|
||||
// Proper logging config will be applied after CLI parsing.
|
||||
// No stdout printing.
|
||||
if let Err(e) = real_main() {
|
||||
// In initramfs, emit minimal error to stderr and exit non-zero.
|
||||
eprintln!("error: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn real_main() -> Result<()> {
|
||||
let cli = cli::from_args();
|
||||
let log_opts = logging::LogOptions::from_cli(&cli);
|
||||
logging::init_logging(&log_opts)?;
|
||||
|
||||
let cfg = config::load_and_merge(&cli)?;
|
||||
config::validate(&cfg)?;
|
||||
|
||||
let ctx = orchestrator::Context::new(cfg, log_opts);
|
||||
orchestrator::run(&ctx)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI
|
||||
|
||||
References
|
||||
- [struct Cli](src/cli/args.rs:1)
|
||||
- [fn from_args() -> Cli](src/cli/args.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use clap::{Parser, ValueEnum};
|
||||
|
||||
/// zosstorage - one-shot disk initializer for initramfs.
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "zosstorage", disable_help_subcommand = true)]
|
||||
pub struct Cli {
|
||||
/// Path to YAML configuration (mirrors kernel cmdline key 'zosstorage.config=')
|
||||
#[arg(long = "config")]
|
||||
pub config: Option<String>,
|
||||
|
||||
/// Log level: error, warn, info, debug
|
||||
#[arg(long = "log-level", default_value = "info")]
|
||||
pub log_level: String,
|
||||
|
||||
/// Also log to /run/zosstorage/zosstorage.log
|
||||
#[arg(long = "log-to-file", default_value_t = false)]
|
||||
pub log_to_file: bool,
|
||||
|
||||
/// Enable writing /etc/fstab entries
|
||||
#[arg(long = "fstab", default_value_t = false)]
|
||||
pub fstab: bool,
|
||||
|
||||
/// Present but non-functional; returns unimplemented error
|
||||
#[arg(long = "force")]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
/// Parse CLI arguments (non-interactive; suitable for initramfs).
|
||||
pub fn from_args() -> Cli {
|
||||
Cli::parse()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logging
|
||||
|
||||
References
|
||||
- [struct LogOptions](src/logging/mod.rs:1)
|
||||
- [fn init_logging(opts: &LogOptions) -> Result<()>](src/logging/mod.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use crate::Result;
|
||||
|
||||
/// Logging options resolved from CLI and/or config.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LogOptions {
|
||||
pub level: String, // "error" | "warn" | "info" | "debug"
|
||||
pub to_file: bool, // when true, logs to /run/zosstorage/zosstorage.log
|
||||
}
|
||||
|
||||
impl LogOptions {
|
||||
pub fn from_cli(cli: &crate::cli::Cli) -> Self {
|
||||
Self { level: cli.log_level.clone(), to_file: cli.log_to_file }
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize tracing subscriber according to options.
|
||||
/// Must be idempotent when called once in process lifetime.
|
||||
pub fn init_logging(opts: &LogOptions) -> Result<()> {
|
||||
todo!("set up tracing for stderr and optional file layer")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration types
|
||||
|
||||
References
|
||||
- [struct Config](src/types.rs:1)
|
||||
- [enum Topology](src/types.rs:1)
|
||||
- [struct DeviceSelection](src/types.rs:1)
|
||||
- [struct Partitioning](src/types.rs:1)
|
||||
- [struct FsOptions](src/types.rs:1)
|
||||
- [struct MountScheme](src/types.rs:1)
|
||||
- [struct ReportOptions](src/types.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoggingConfig {
|
||||
pub level: String, // default "info"
|
||||
pub to_file: bool, // default false
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DeviceSelection {
|
||||
pub include_patterns: Vec<String>,
|
||||
pub exclude_patterns: Vec<String>,
|
||||
pub allow_removable: bool,
|
||||
pub min_size_gib: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Topology {
|
||||
Single,
|
||||
DualIndependent,
|
||||
SsdHddBcachefs,
|
||||
BtrfsRaid1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BiosBootSpec {
|
||||
pub enabled: bool,
|
||||
pub size_mib: u64,
|
||||
pub gpt_name: String, // "zosboot"
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EspSpec {
|
||||
pub size_mib: u64,
|
||||
pub label: String, // "ZOSBOOT"
|
||||
pub gpt_name: String, // "zosboot"
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DataSpec {
|
||||
pub gpt_name: String, // "zosdata"
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CacheSpec {
|
||||
pub gpt_name: String, // "zoscache"
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Partitioning {
|
||||
pub alignment_mib: u64,
|
||||
pub require_empty_disks: bool,
|
||||
pub bios_boot: BiosBootSpec,
|
||||
pub esp: EspSpec,
|
||||
pub data: DataSpec,
|
||||
pub cache: CacheSpec,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BtrfsOptions {
|
||||
pub label: String, // "ZOSDATA"
|
||||
pub compression: String, // "zstd:3"
|
||||
pub raid_profile: String // "none" | "raid1"
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BcachefsOptions {
|
||||
pub label: String, // "ZOSDATA"
|
||||
pub cache_mode: String, // "promote" | "writeback?"
|
||||
pub compression: String, // "zstd"
|
||||
pub checksum: String, // "crc32c"
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VfatOptions {
|
||||
pub label: String, // "ZOSBOOT"
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FsOptions {
|
||||
pub btrfs: BtrfsOptions,
|
||||
pub bcachefs: BcachefsOptions,
|
||||
pub vfat: VfatOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MountSchemeKind {
|
||||
PerUuid,
|
||||
Custom, // reserved
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MountScheme {
|
||||
pub base_dir: String, // "/var/cache"
|
||||
pub scheme: MountSchemeKind,
|
||||
pub fstab_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReportOptions {
|
||||
pub path: String, // "/run/zosstorage/state.json"
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub version: u32,
|
||||
pub logging: LoggingConfig,
|
||||
pub device_selection: DeviceSelection,
|
||||
pub topology: Topology,
|
||||
pub partitioning: Partitioning,
|
||||
pub filesystem: FsOptions,
|
||||
pub mount: MountScheme,
|
||||
pub report: ReportOptions,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration I/O
|
||||
|
||||
References
|
||||
- [fn load_and_merge(cli: &Cli) -> Result<Config>](src/config/loader.rs:1)
|
||||
- [fn validate(cfg: &Config) -> Result<()>](src/config/loader.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use crate::{cli::Cli, Result};
|
||||
|
||||
/// Load defaults, merge file config, overlay CLI, and finally kernel cmdline.
|
||||
pub fn load_and_merge(cli: &Cli) -> Result<crate::config::types::Config> {
|
||||
todo!("implement precedence: file < CLI < kernel cmdline key 'zosstorage.config='")
|
||||
}
|
||||
|
||||
/// Validate semantic correctness of the configuration.
|
||||
pub fn validate(cfg: &crate::config::types::Config) -> Result<()> {
|
||||
todo!("ensure device filters, sizes, topology combinations are valid")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Device discovery
|
||||
|
||||
References
|
||||
- [struct Disk](src/device/discovery.rs:1)
|
||||
- [struct DeviceFilter](src/device/discovery.rs:1)
|
||||
- [trait DeviceProvider](src/device/discovery.rs:1)
|
||||
- [fn discover(filter: &DeviceFilter) -> Result<Vec<Disk>>](src/device/discovery.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use crate::Result;
|
||||
|
||||
/// Eligible block device.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Disk {
|
||||
pub path: String,
|
||||
pub size_bytes: u64,
|
||||
pub rotational: bool,
|
||||
pub model: Option<String>,
|
||||
pub serial: Option<String>,
|
||||
}
|
||||
|
||||
/// Compiled device filters from config patterns.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeviceFilter {
|
||||
pub include: Vec<regex::Regex>,
|
||||
pub exclude: Vec<regex::Regex>,
|
||||
pub min_size_gib: u64,
|
||||
}
|
||||
|
||||
/// Abstract provider for devices to enable testing with doubles.
|
||||
pub trait DeviceProvider {
|
||||
fn list_block_devices(&self) -> Result<Vec<Disk>>;
|
||||
fn probe_properties(&self, disk: &mut Disk) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Discover eligible disks according to the filter policy.
|
||||
pub fn discover(filter: &DeviceFilter) -> Result<Vec<Disk>> {
|
||||
todo!("enumerate /dev, apply include/exclude, probe properties")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Partitioning
|
||||
|
||||
References
|
||||
- [enum PartRole](src/partition/plan.rs:1)
|
||||
- [struct PartitionSpec](src/partition/plan.rs:1)
|
||||
- [struct PartitionPlan](src/partition/plan.rs:1)
|
||||
- [struct PartitionResult](src/partition/plan.rs:1)
|
||||
- [fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result<PartitionPlan>](src/partition/plan.rs:1)
|
||||
- [fn apply_partitions(plan: &PartitionPlan) -> Result<Vec<PartitionResult>>](src/partition/plan.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use crate::{config::types::Config, device::Disk, Result};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum PartRole {
|
||||
BiosBoot,
|
||||
Esp,
|
||||
Data,
|
||||
Cache,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PartitionSpec {
|
||||
pub role: PartRole,
|
||||
pub size_mib: Option<u64>, // None means "remainder"
|
||||
pub gpt_name: String, // zosboot | zosdata | zoscache
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiskPlan {
|
||||
pub disk: Disk,
|
||||
pub parts: Vec<PartitionSpec>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PartitionPlan {
|
||||
pub alignment_mib: u64,
|
||||
pub disks: Vec<DiskPlan>,
|
||||
pub require_empty_disks: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PartitionResult {
|
||||
pub disk: String,
|
||||
pub part_number: u32,
|
||||
pub role: PartRole,
|
||||
pub gpt_name: String,
|
||||
pub uuid: String,
|
||||
pub start_mib: u64,
|
||||
pub size_mib: u64,
|
||||
pub device_path: String, // e.g., /dev/nvme0n1p2
|
||||
}
|
||||
|
||||
/// Compute GPT-only plan per topology and constraints.
|
||||
pub fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result<PartitionPlan> {
|
||||
todo!("layout bios boot, ESP, data (and cache for SSD/HDD); align to 1 MiB")
|
||||
}
|
||||
|
||||
/// Apply plan using sgdisk and verify via blkid; require empty disks if configured.
|
||||
pub fn apply_partitions(plan: &PartitionPlan) -> Result<Vec<PartitionResult>> {
|
||||
todo!("shell out to sgdisk, trigger udev settle, collect partition GUIDs")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Filesystems
|
||||
|
||||
References
|
||||
- [enum FsKind](src/fs/plan.rs:1)
|
||||
- [struct FsSpec](src/fs/plan.rs:1)
|
||||
- [struct FsPlan](src/fs/plan.rs:1)
|
||||
- [struct FsResult](src/fs/plan.rs:1)
|
||||
- [fn plan_filesystems(...)](src/fs/plan.rs:1)
|
||||
- [fn make_filesystems(...)](src/fs/plan.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use crate::{partition::PartitionResult, config::types::Config, Result};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum FsKind {
|
||||
Vfat,
|
||||
Btrfs,
|
||||
Bcachefs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FsSpec {
|
||||
pub kind: FsKind,
|
||||
pub devices: Vec<String>, // 1 device for vfat/btrfs; 2 for bcachefs (cache + backing)
|
||||
pub label: String, // "ZOSBOOT" (vfat) or "ZOSDATA" (data)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FsPlan {
|
||||
pub specs: Vec<FsSpec>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FsResult {
|
||||
pub kind: FsKind,
|
||||
pub devices: Vec<String>,
|
||||
pub uuid: String,
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
/// Decide which partitions get which filesystem based on topology.
|
||||
pub fn plan_filesystems(
|
||||
parts: &[PartitionResult],
|
||||
cfg: &Config,
|
||||
) -> Result<FsPlan> {
|
||||
todo!("map ESP to vfat, data to btrfs or bcachefs according to topology")
|
||||
}
|
||||
|
||||
/// Create the filesystems and return identity info (UUIDs, labels).
|
||||
pub fn make_filesystems(plan: &FsPlan) -> Result<Vec<FsResult>> {
|
||||
todo!("invoke mkfs tools with configured options via util::run_cmd")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mounting
|
||||
|
||||
References
|
||||
- [struct MountPlan](src/mount/ops.rs:1)
|
||||
- [struct MountResult](src/mount/ops.rs:1)
|
||||
- [fn plan_mounts(...)](src/mount/ops.rs:1)
|
||||
- [fn apply_mounts(...)](src/mount/ops.rs:1)
|
||||
- [fn maybe_write_fstab(...)](src/mount/ops.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use crate::{fs::FsResult, config::types::Config, Result};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MountPlan {
|
||||
pub entries: Vec<(String /* source */, String /* target */, String /* fstype */, String /* options */)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MountResult {
|
||||
pub source: String,
|
||||
pub target: String,
|
||||
pub fstype: String,
|
||||
pub options: String,
|
||||
}
|
||||
|
||||
/// Build mount plan under /var/cache/<UUID> by default.
|
||||
pub fn plan_mounts(fs_results: &[FsResult], cfg: &Config) -> Result<MountPlan> {
|
||||
todo!("create per-UUID directories and mount mapping")
|
||||
}
|
||||
|
||||
/// Apply mounts using syscalls (nix), ensuring directories exist.
|
||||
pub fn apply_mounts(plan: &MountPlan) -> Result<Vec<MountResult>> {
|
||||
todo!("perform mount syscalls and return results")
|
||||
}
|
||||
|
||||
/// Optionally generate /etc/fstab entries in deterministic order.
|
||||
pub fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config) -> Result<()> {
|
||||
todo!("when enabled, write fstab entries")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reporting
|
||||
|
||||
References
|
||||
- [const REPORT_VERSION: &str](src/report/state.rs:1)
|
||||
- [struct StateReport](src/report/state.rs:1)
|
||||
- [fn build_report(...)](src/report/state.rs:1)
|
||||
- [fn write_report(...)](src/report/state.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::{device::Disk, partition::PartitionResult, fs::FsResult, mount::MountResult, Result};
|
||||
|
||||
pub const REPORT_VERSION: &str = "v1";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StateReport {
|
||||
pub version: String,
|
||||
pub timestamp: String, // RFC3339
|
||||
pub status: String, // "success" | "already_provisioned" | "error"
|
||||
pub disks: Vec<serde_json::Value>,
|
||||
pub partitions: Vec<serde_json::Value>,
|
||||
pub filesystems: Vec<serde_json::Value>,
|
||||
pub mounts: Vec<serde_json::Value>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
/// Build the machine-readable state report.
|
||||
pub fn build_report(
|
||||
disks: &[Disk],
|
||||
parts: &[PartitionResult],
|
||||
fs: &[FsResult],
|
||||
mounts: &[MountResult],
|
||||
status: &str,
|
||||
) -> StateReport {
|
||||
todo!("assemble structured report in v1 format")
|
||||
}
|
||||
|
||||
/// Write the state report to the configured path (default /run/zosstorage/state.json).
|
||||
pub fn write_report(report: &StateReport, path: &str) -> Result<()> {
|
||||
todo!("serialize to JSON and persist atomically")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Orchestrator
|
||||
|
||||
References
|
||||
- [struct Context](src/orchestrator/run.rs:1)
|
||||
- [fn run(ctx: &Context) -> Result<()>](src/orchestrator/run.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use crate::{config::types::Config, logging::LogOptions, Result};
|
||||
|
||||
/// Execution context holding resolved configuration and environment flags.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Context {
|
||||
pub cfg: Config,
|
||||
pub log: LogOptions,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(cfg: Config, log: LogOptions) -> Self { Self { cfg, log } }
|
||||
}
|
||||
|
||||
/// High-level one-shot flow; aborts on any validation failure.
|
||||
/// Returns Ok(()) on success and also on no-op when already provisioned.
|
||||
pub fn run(ctx: &Context) -> Result<()> {
|
||||
todo!("idempotency check, discovery, planning, application, reporting")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Idempotency
|
||||
|
||||
References
|
||||
- [fn detect_existing_state() -> Result<Option<StateReport>>](src/idempotency/mod.rs:1)
|
||||
- [fn is_empty_disk(disk: &Disk) -> Result<bool>](src/idempotency/mod.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use crate::{device::Disk, report::StateReport, Result};
|
||||
|
||||
/// Return existing state if system is already provisioned; otherwise None.
|
||||
pub fn detect_existing_state() -> Result<Option<StateReport>> {
|
||||
todo!("probe GPT names (zosboot, zosdata, zoscache) and FS labels (ZOSBOOT, ZOSDATA)")
|
||||
}
|
||||
|
||||
/// Determine if a disk is empty (no partitions and no known FS signatures).
|
||||
pub fn is_empty_disk(disk: &Disk) -> Result<bool> {
|
||||
todo!("use blkid and partition table inspection to declare emptiness")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Utilities
|
||||
|
||||
References
|
||||
- [struct CmdOutput](src/util/mod.rs:1)
|
||||
- [fn which_tool(name: &str) -> Result<Option<String>>](src/util/mod.rs:1)
|
||||
- [fn run_cmd(args: &[&str]) -> Result<()>](src/util/mod.rs:1)
|
||||
- [fn run_cmd_capture(args: &[&str]) -> Result<CmdOutput>](src/util/mod.rs:1)
|
||||
- [fn udev_settle(timeout_ms: u64) -> Result<()>](src/util/mod.rs:1)
|
||||
|
||||
Skeleton
|
||||
```rust
|
||||
use crate::Result;
|
||||
|
||||
/// Captured output from an external tool invocation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CmdOutput {
|
||||
pub status: i32,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
||||
|
||||
/// Locate the absolute path to required tool if available.
|
||||
pub fn which_tool(name: &str) -> Result<Option<String>> {
|
||||
todo!("use 'which' crate or manual PATH scanning")
|
||||
}
|
||||
|
||||
/// Run a command and return Ok if the exit status is zero.
|
||||
pub fn run_cmd(args: &[&str]) -> Result<()> {
|
||||
todo!("spawn process, log stderr on failure, map to Error::Tool")
|
||||
}
|
||||
|
||||
/// Run a command and capture stdout/stderr for parsing (e.g., blkid).
|
||||
pub fn run_cmd_capture(args: &[&str]) -> Result<CmdOutput> {
|
||||
todo!("spawn process and collect output")
|
||||
}
|
||||
|
||||
/// Call udevadm settle with a timeout; warn if unavailable, then no-op.
|
||||
pub fn udev_settle(timeout_ms: u64) -> Result<()> {
|
||||
todo!("invoke udevadm settle when present")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Approval gate
|
||||
- This API skeleton is ready for implementation as source files with todo!() bodies.
|
||||
- Upon approval, we will:
|
||||
- Create the src/ files as outlined.
|
||||
- Add dependencies via cargo add.
|
||||
- Ensure all modules compile with placeholders.
|
||||
- Add initial tests scaffolding and example configs.
|
||||
|
||||
References summary
|
||||
- [fn main()](src/main.rs:1)
|
||||
- [fn from_args()](src/cli/args.rs:1)
|
||||
- [fn init_logging(opts: &LogOptions)](src/logging/mod.rs:1)
|
||||
- [fn load_and_merge(cli: &Cli)](src/config/loader.rs:1)
|
||||
- [fn validate(cfg: &Config)](src/config/loader.rs:1)
|
||||
- [fn discover(filter: &DeviceFilter)](src/device/discovery.rs:1)
|
||||
- [fn plan_partitions(disks: &[Disk], cfg: &Config)](src/partition/plan.rs:1)
|
||||
- [fn apply_partitions(plan: &PartitionPlan)](src/partition/plan.rs:1)
|
||||
- [fn plan_filesystems(parts: &[PartitionResult], cfg: &Config)](src/fs/plan.rs:1)
|
||||
- [fn make_filesystems(plan: &FsPlan)](src/fs/plan.rs:1)
|
||||
- [fn plan_mounts(fs_results: &[FsResult], cfg: &Config)](src/mount/ops.rs:1)
|
||||
- [fn apply_mounts(plan: &MountPlan)](src/mount/ops.rs:1)
|
||||
- [fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config)](src/mount/ops.rs:1)
|
||||
- [fn build_report(...)](src/report/state.rs:1)
|
||||
- [fn write_report(report: &StateReport)](src/report/state.rs:1)
|
||||
- [fn detect_existing_state()](src/idempotency/mod.rs:1)
|
||||
- [fn is_empty_disk(disk: &Disk)](src/idempotency/mod.rs:1)
|
||||
- [fn which_tool(name: &str)](src/util/mod.rs:1)
|
||||
- [fn run_cmd(args: &[&str])](src/util/mod.rs:1)
|
||||
- [fn run_cmd_capture(args: &[&str])](src/util/mod.rs:1)
|
||||
- [fn udev_settle(timeout_ms: u64)](src/util/mod.rs:1)
|
||||
Reference in New Issue
Block a user