mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-21 01:53:30 +00:00
1740 lines
62 KiB
Rust
1740 lines
62 KiB
Rust
// Copyright 2024 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! CLI definitions and parsing logic.
|
|
//!
|
|
//! This module contains the command-line interface definitions including:
|
|
//! - `Cli`: Main CLI parser
|
|
//! - `Commands`: Top-level server and diagnostic subcommands
|
|
//! - `ServerOpts`: Server subcommand options
|
|
//! - `InfoOpts`: Info subcommand options
|
|
//! - `TlsOpts`: TLS diagnostic subcommand options
|
|
//! - `InfoType`: Information type enum
|
|
//! - `CommandResult`: Result of parsing command line arguments
|
|
|
|
use crate::version::{self, build};
|
|
use clap::builder::NonEmptyStringValueParser;
|
|
use clap::{Args, Parser, Subcommand, ValueEnum};
|
|
use const_str::concat;
|
|
use rustfs_config::{DEFAULT_ADDRESS, DEFAULT_CONSOLE_ADDRESS, DEFAULT_CONSOLE_ENABLE, DEFAULT_OBS_ENDPOINT, ENV_RUSTFS_VOLUMES};
|
|
use std::path::PathBuf;
|
|
// build module is re-exported from crate::build
|
|
|
|
pub(super) const SHORT_VERSION: &str = version::DISPLAY_VERSION;
|
|
|
|
pub(super) const LONG_VERSION: &str = concat!(
|
|
concat!(SHORT_VERSION, "\n"),
|
|
concat!("build time : ", build::BUILD_TIME, "\n"),
|
|
concat!("build profile: ", build::BUILD_RUST_CHANNEL, "\n"),
|
|
concat!("build os : ", build::BUILD_OS, "\n"),
|
|
concat!("rust version : ", build::RUST_VERSION, "\n"),
|
|
concat!("rust channel : ", build::RUST_CHANNEL, "\n"),
|
|
concat!("git branch : ", build::BRANCH, "\n"),
|
|
concat!("git commit : ", build::COMMIT_HASH, "\n"),
|
|
concat!("git tag : ", build::TAG, "\n"),
|
|
concat!("git status :\n", build::GIT_STATUS_FILE),
|
|
);
|
|
|
|
/// Known subcommands. When the first arg matches one of these, it is treated as a subcommand.
|
|
pub const KNOWN_SUBCOMMANDS: &[&str] = &["server", "info", "tls", "diagnose", "inspect", "connect"];
|
|
|
|
/// Preprocess argv for legacy compatibility: `rustfs <volume>` and `rustfs --address ...` are
|
|
/// treated as `rustfs server <volume>` and `rustfs server --address ...` respectively.
|
|
/// Also: `rustfs` with no args becomes `rustfs server` (volumes from env), and `rustfs --info`
|
|
/// is treated as `rustfs info`.
|
|
pub fn preprocess_args_for_legacy(args: Vec<String>) -> Vec<String> {
|
|
if args.len() < 2 {
|
|
// rustfs -> rustfs server (volumes from RUSTFS_VOLUMES env)
|
|
return vec![args[0].clone(), "server".to_string()];
|
|
}
|
|
let first = &args[1];
|
|
// If first arg looks like a subcommand, do nothing
|
|
if KNOWN_SUBCOMMANDS.contains(&first.as_str()) {
|
|
return args;
|
|
}
|
|
// Preserve the traditional `rustfs help` entry point without exposing
|
|
// Clap's generated `help` subcommand in the top-level command list.
|
|
if first == "help" {
|
|
let mut out = vec![args[0].clone()];
|
|
out.extend(args[2..].iter().cloned());
|
|
out.push("--help".to_string());
|
|
return out;
|
|
}
|
|
// If first arg is --info, treat it as info subcommand
|
|
if first == "--info" {
|
|
let mut out = vec![args[0].clone(), "info".to_string()];
|
|
out.extend(args[2..].iter().cloned());
|
|
return out;
|
|
}
|
|
// If first arg is a global flag (--help, --version), do nothing
|
|
if first == "--help" || first == "-h" || first == "--version" || first == "-V" {
|
|
return args;
|
|
}
|
|
// Legacy: rustfs <volume> or rustfs --address ... -> rustfs server <volume|--address ...>
|
|
let mut out = vec![args[0].clone(), "server".to_string()];
|
|
out.extend(args[1..].iter().cloned());
|
|
out
|
|
}
|
|
|
|
/// Main CLI parser
|
|
#[derive(Parser, Clone)]
|
|
#[command(name = "rustfs", version = SHORT_VERSION, long_version = LONG_VERSION)]
|
|
#[command(disable_help_subcommand = true)]
|
|
pub struct Cli {
|
|
#[command(subcommand)]
|
|
pub command: Option<Commands>,
|
|
}
|
|
|
|
/// Available subcommands
|
|
#[derive(Subcommand, Clone)]
|
|
pub enum Commands {
|
|
/// Start the object storage server (default when no subcommand is given)
|
|
Server(Box<ServerOpts>),
|
|
/// Display system information
|
|
Info(InfoOpts),
|
|
/// Inspect TLS certificate directory layout and parsing status
|
|
Tls(TlsOpts),
|
|
/// Analyze RustFS log files and report probable failure causes
|
|
Diagnose(DiagnoseOpts),
|
|
/// Offline, read-only inspection of on-disk data (no server required)
|
|
Inspect(InspectOpts),
|
|
/// Configure outbound RustFS Connect integration
|
|
Connect(ConnectOpts),
|
|
}
|
|
|
|
/// RustFS Connect subcommand options
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectOpts {
|
|
#[command(subcommand)]
|
|
pub command: ConnectCommands,
|
|
}
|
|
|
|
/// Allow-listed RustFS Connect operations
|
|
#[derive(Subcommand, Clone)]
|
|
pub enum ConnectCommands {
|
|
/// Exchange a protected one-time token for a durable device credential (Unix only)
|
|
Register(ConnectRegisterOpts),
|
|
/// Import, verify, or inspect a signed Connect service license
|
|
License(ConnectLicenseOpts),
|
|
/// Read the persisted deployment inventory and collect an approved environment summary
|
|
Inventory(ConnectInventoryOpts),
|
|
/// Run an explicitly approved, bounded local performance measurement
|
|
Performance(ConnectPerformanceOpts),
|
|
/// Capture a consent-bound local profile and write a signed export
|
|
Profile(ConnectProfileOpts),
|
|
/// Capture allow-listed local log events and write a signed export
|
|
Logs(ConnectLogsOpts),
|
|
/// Record, forward, or replay consent-bound telemetry
|
|
Telemetry(ConnectTelemetryOpts),
|
|
/// Capture a consent-bound local top snapshot
|
|
Top(ConnectTopOpts),
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectInventoryOpts {
|
|
#[command(subcommand)]
|
|
pub command: ConnectInventoryCommands,
|
|
}
|
|
|
|
#[derive(Subcommand, Clone)]
|
|
pub enum ConnectInventoryCommands {
|
|
/// Collect the bounded inventory.environment@1 summary
|
|
Environment(ConnectEnvironmentInventoryOpts),
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectEnvironmentInventoryOpts {
|
|
/// Directory containing the persisted Connect inventory
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
/// Negotiated environment schema version
|
|
#[arg(long = "schema-version", default_value_t = 1)]
|
|
pub schema_version: u16,
|
|
/// Negotiated environment capability
|
|
#[arg(long, default_value = "inventory.environment@1", value_parser = NonEmptyStringValueParser::new())]
|
|
pub capability: String,
|
|
/// Maximum collection time in seconds
|
|
#[arg(long = "timeout-seconds", default_value_t = 30)]
|
|
pub timeout_seconds: u64,
|
|
/// Confirm this explicit local L1 inventory operation
|
|
#[arg(long = "acknowledge-l1", required = true, action = clap::ArgAction::SetTrue)]
|
|
pub acknowledge_l1: bool,
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectTopOpts {
|
|
#[command(subcommand)]
|
|
pub command: ConnectTopCommands,
|
|
}
|
|
|
|
#[derive(Subcommand, Clone)]
|
|
pub enum ConnectTopCommands {
|
|
/// Capture API activity when an approved typed source is available
|
|
Api(ConnectTopCaptureOpts),
|
|
/// Capture process disk I/O on supported platforms
|
|
Disk(ConnectTopCaptureOpts),
|
|
/// Capture lock activity when an approved typed source is available
|
|
Locks(ConnectTopCaptureOpts),
|
|
/// Capture internode network traffic
|
|
Net(ConnectTopCaptureOpts),
|
|
/// Capture RPC activity when an approved typed source is available
|
|
Rpc(ConnectTopCaptureOpts),
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectTopCaptureOpts {
|
|
/// Directory containing an enrolled Connect device identity
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
/// New local archive path; an existing file is never replaced
|
|
#[arg(long)]
|
|
pub output: PathBuf,
|
|
/// Organization resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub organization: String,
|
|
/// Cluster resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub cluster: String,
|
|
/// Cluster-device resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub device: String,
|
|
/// UUIDv7 diagnostic run identifier issued by Connect
|
|
#[arg(long = "run-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub run_uid: String,
|
|
/// UUIDv7 artifact identifier issued by Connect
|
|
#[arg(long = "artifact-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub artifact_uid: String,
|
|
/// UUIDv7 consent identifier issued by Connect
|
|
#[arg(long = "consent-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub consent_uid: String,
|
|
/// Consent policy revision bound to this capture
|
|
#[arg(long = "policy-revision")]
|
|
pub policy_revision: u64,
|
|
/// Consent expiry as UTC Unix seconds
|
|
#[arg(long = "consent-expires-at")]
|
|
pub consent_expires_at_unix: i64,
|
|
/// Diagnostic run expiry as UTC Unix seconds
|
|
#[arg(long = "run-expires-at")]
|
|
pub run_expires_at_unix: i64,
|
|
/// Monotonic sampling window in milliseconds
|
|
#[arg(long = "window-millis", default_value_t = 1_000)]
|
|
pub window_millis: u64,
|
|
/// Signed export validity in seconds
|
|
#[arg(long = "export-validity-seconds", default_value_t = 300)]
|
|
pub export_validity_seconds: u64,
|
|
/// Confirm this explicit local L3 diagnostic operation
|
|
#[arg(long = "acknowledge-l3", required = true, action = clap::ArgAction::SetTrue)]
|
|
pub acknowledge_l3: bool,
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectPerformanceOpts {
|
|
#[command(subcommand)]
|
|
pub command: ConnectPerformanceCommands,
|
|
}
|
|
|
|
#[derive(Subcommand, Clone)]
|
|
pub enum ConnectPerformanceCommands {
|
|
/// Measure bounded client-to-deployment transfer performance
|
|
Client(Box<ConnectClientPerformanceOpts>),
|
|
/// Measure bounded S3 object throughput in a dedicated temporary namespace
|
|
Object(Box<ConnectObjectPerformanceOpts>),
|
|
/// Measure bounded destination-confirmed site-replication performance
|
|
SiteReplication(Box<ConnectSiteReplicationPerformanceOpts>),
|
|
/// Measure generated-file write and warm page-cache read performance
|
|
Drive(Box<ConnectDrivePerformanceOpts>),
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectSiteReplicationPerformanceOpts {
|
|
/// Directory containing an enrolled Connect device identity
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
/// Source RustFS deployment endpoint
|
|
#[arg(long = "source-endpoint", value_parser = NonEmptyStringValueParser::new())]
|
|
pub source_endpoint: String,
|
|
/// Destination RustFS deployment endpoint
|
|
#[arg(long = "destination-endpoint", value_parser = NonEmptyStringValueParser::new())]
|
|
pub destination_endpoint: String,
|
|
/// Optional PEM root certificate for the source endpoint
|
|
#[arg(long = "source-ca-file")]
|
|
pub source_ca_file: Option<PathBuf>,
|
|
/// Optional PEM root certificate for the destination endpoint
|
|
#[arg(long = "destination-ca-file")]
|
|
pub destination_ca_file: Option<PathBuf>,
|
|
/// Owner-readable file containing the source S3 access key
|
|
#[arg(long = "source-access-key-file")]
|
|
pub source_access_key_file: PathBuf,
|
|
/// Owner-readable file containing the source S3 secret key
|
|
#[arg(long = "source-secret-key-file")]
|
|
pub source_secret_key_file: PathBuf,
|
|
/// Optional owner-readable file containing a source S3 session token
|
|
#[arg(long = "source-session-token-file")]
|
|
pub source_session_token_file: Option<PathBuf>,
|
|
/// Owner-readable file containing the destination S3 access key
|
|
#[arg(long = "destination-access-key-file")]
|
|
pub destination_access_key_file: PathBuf,
|
|
/// Owner-readable file containing the destination S3 secret key
|
|
#[arg(long = "destination-secret-key-file")]
|
|
pub destination_secret_key_file: PathBuf,
|
|
/// Optional owner-readable file containing a destination S3 session token
|
|
#[arg(long = "destination-session-token-file")]
|
|
pub destination_session_token_file: Option<PathBuf>,
|
|
/// New local archive path; an existing file is never replaced
|
|
#[arg(long)]
|
|
pub output: PathBuf,
|
|
/// Negotiated producer schema version
|
|
#[arg(long = "schema-version", default_value_t = 1)]
|
|
pub schema_version: u16,
|
|
/// Negotiated producer capability
|
|
#[arg(long, default_value = "performance.siteReplication@1", value_parser = NonEmptyStringValueParser::new())]
|
|
pub capability: String,
|
|
/// Organization resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub organization: String,
|
|
/// Cluster resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub cluster: String,
|
|
/// Cluster-device resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub device: String,
|
|
/// UUIDv7 diagnostic run identifier issued by Connect
|
|
#[arg(long = "run-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub run_uid: String,
|
|
/// UUIDv7 artifact identifier issued by Connect
|
|
#[arg(long = "artifact-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub artifact_uid: String,
|
|
/// UUIDv7 consent identifier issued by Connect
|
|
#[arg(long = "consent-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub consent_uid: String,
|
|
/// Consent policy revision bound to this measurement
|
|
#[arg(long = "policy-revision")]
|
|
pub policy_revision: u64,
|
|
/// Consent expiry as UTC Unix seconds
|
|
#[arg(long = "consent-expires-at")]
|
|
pub consent_expires_at_unix: i64,
|
|
/// Artifact expiry as UTC Unix seconds
|
|
#[arg(long = "expires-at")]
|
|
pub expires_at_unix: i64,
|
|
/// Generated transfer size in bytes
|
|
#[arg(long = "traffic-bytes", default_value_t = 65_536)]
|
|
pub traffic_bytes: u64,
|
|
/// Maximum wall-clock duration in milliseconds
|
|
#[arg(long = "duration-millis", default_value_t = 5_000)]
|
|
pub duration_millis: u64,
|
|
/// Stable opaque alias for the source deployment
|
|
#[arg(long = "source-alias", value_parser = NonEmptyStringValueParser::new())]
|
|
pub source_alias: String,
|
|
/// Source deployment identifier from site-replication configuration
|
|
#[arg(long = "source-deployment-id", value_parser = NonEmptyStringValueParser::new())]
|
|
pub source_deployment_id: String,
|
|
/// Stable opaque alias for the destination deployment
|
|
#[arg(long = "destination-alias", value_parser = NonEmptyStringValueParser::new())]
|
|
pub destination_alias: String,
|
|
/// Destination deployment identifier from site-replication configuration
|
|
#[arg(long = "destination-deployment-id", value_parser = NonEmptyStringValueParser::new())]
|
|
pub destination_deployment_id: String,
|
|
/// Existing versioned bucket dedicated to disposable performance objects
|
|
#[arg(long = "scratch-bucket", value_parser = NonEmptyStringValueParser::new())]
|
|
pub scratch_bucket: String,
|
|
/// Bounded cleanup window for versions that arrive after cancellation
|
|
#[arg(long = "late-arrival-cleanup-millis", default_value_t = 1_000)]
|
|
pub late_arrival_cleanup_millis: u64,
|
|
/// Confirm this explicit local L2 diagnostic operation
|
|
#[arg(long = "acknowledge-l2", required = true, action = clap::ArgAction::SetTrue)]
|
|
pub acknowledge_l2: bool,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
|
|
pub enum ConnectClientPerformanceOperation {
|
|
Get,
|
|
Put,
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectClientPerformanceOpts {
|
|
/// Directory containing an enrolled Connect device identity
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
|
|
/// RustFS deployment endpoint
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub endpoint: String,
|
|
|
|
/// Optional PEM root certificate for the deployment endpoint
|
|
#[arg(long = "ca-file")]
|
|
pub ca_file: Option<PathBuf>,
|
|
|
|
/// Optional explicit HTTP(S) proxy without embedded credentials
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub proxy: Option<String>,
|
|
|
|
/// Owner-readable file containing the S3 access key
|
|
#[arg(long = "access-key-file")]
|
|
pub access_key_file: PathBuf,
|
|
|
|
/// Owner-readable file containing the S3 secret key
|
|
#[arg(long = "secret-key-file")]
|
|
pub secret_key_file: PathBuf,
|
|
|
|
/// Optional owner-readable file containing an S3 session token
|
|
#[arg(long = "session-token-file")]
|
|
pub session_token_file: Option<PathBuf>,
|
|
|
|
/// New local archive path; an existing file is never replaced
|
|
#[arg(long)]
|
|
pub output: PathBuf,
|
|
|
|
/// Negotiated producer schema version
|
|
#[arg(long = "schema-version", default_value_t = 1)]
|
|
pub schema_version: u16,
|
|
|
|
/// Negotiated producer capability
|
|
#[arg(long, default_value = "performance.client@1", value_parser = NonEmptyStringValueParser::new())]
|
|
pub capability: String,
|
|
|
|
/// Organization resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub organization: String,
|
|
|
|
/// Cluster resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub cluster: String,
|
|
|
|
/// Cluster-device resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub device: String,
|
|
|
|
/// UUIDv7 diagnostic run identifier issued by Connect
|
|
#[arg(long = "run-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub run_uid: String,
|
|
|
|
/// UUIDv7 artifact identifier issued by Connect
|
|
#[arg(long = "artifact-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub artifact_uid: String,
|
|
|
|
/// UUIDv7 consent identifier issued by Connect
|
|
#[arg(long = "consent-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub consent_uid: String,
|
|
|
|
/// Consent policy revision bound to this measurement
|
|
#[arg(long = "policy-revision")]
|
|
pub policy_revision: u64,
|
|
|
|
/// Consent expiry as UTC Unix seconds
|
|
#[arg(long = "consent-expires-at")]
|
|
pub consent_expires_at_unix: i64,
|
|
|
|
/// Artifact expiry as UTC Unix seconds
|
|
#[arg(long = "expires-at")]
|
|
pub expires_at_unix: i64,
|
|
|
|
/// Client transfer operation
|
|
#[arg(long, value_enum)]
|
|
pub operation: ConnectClientPerformanceOperation,
|
|
|
|
/// Generated transfer size in bytes
|
|
#[arg(long = "traffic-bytes", default_value_t = 65_536)]
|
|
pub traffic_bytes: u64,
|
|
|
|
/// Maximum wall-clock duration in milliseconds
|
|
#[arg(long = "duration-millis", default_value_t = 1_000)]
|
|
pub duration_millis: u64,
|
|
|
|
/// Stable opaque alias for the deployment target
|
|
#[arg(long = "target-alias", default_value = "deployment-1", value_parser = NonEmptyStringValueParser::new())]
|
|
pub target_alias: String,
|
|
|
|
/// Confirm this explicit local L1 diagnostic operation
|
|
#[arg(long = "acknowledge-l1", required = true, action = clap::ArgAction::SetTrue)]
|
|
pub acknowledge_l1: bool,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
|
|
pub enum ConnectObjectPerformanceOperation {
|
|
Get,
|
|
Put,
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectObjectPerformanceOpts {
|
|
/// Directory containing an enrolled Connect device identity
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
|
|
/// RustFS deployment endpoint
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub endpoint: String,
|
|
|
|
/// Optional PEM root certificate for the deployment endpoint
|
|
#[arg(long = "ca-file")]
|
|
pub ca_file: Option<PathBuf>,
|
|
|
|
/// Optional explicit HTTP(S) proxy without embedded credentials
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub proxy: Option<String>,
|
|
|
|
/// Owner-readable file containing the S3 access key
|
|
#[arg(long = "access-key-file")]
|
|
pub access_key_file: PathBuf,
|
|
|
|
/// Owner-readable file containing the S3 secret key
|
|
#[arg(long = "secret-key-file")]
|
|
pub secret_key_file: PathBuf,
|
|
|
|
/// Optional owner-readable file containing an S3 session token
|
|
#[arg(long = "session-token-file")]
|
|
pub session_token_file: Option<PathBuf>,
|
|
|
|
/// New local archive path; an existing file is never replaced
|
|
#[arg(long)]
|
|
pub output: PathBuf,
|
|
|
|
/// Negotiated producer schema version
|
|
#[arg(long = "schema-version", default_value_t = 1)]
|
|
pub schema_version: u16,
|
|
|
|
/// Negotiated producer capability
|
|
#[arg(long, default_value = "performance.object@1", value_parser = NonEmptyStringValueParser::new())]
|
|
pub capability: String,
|
|
|
|
/// Organization resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub organization: String,
|
|
|
|
/// Cluster resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub cluster: String,
|
|
|
|
/// Cluster-device resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub device: String,
|
|
|
|
/// UUIDv7 diagnostic run identifier issued by Connect
|
|
#[arg(long = "run-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub run_uid: String,
|
|
|
|
/// UUIDv7 artifact identifier issued by Connect
|
|
#[arg(long = "artifact-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub artifact_uid: String,
|
|
|
|
/// UUIDv7 consent identifier issued by Connect
|
|
#[arg(long = "consent-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub consent_uid: String,
|
|
|
|
/// Consent policy revision bound to this measurement
|
|
#[arg(long = "policy-revision")]
|
|
pub policy_revision: u64,
|
|
|
|
/// Consent expiry as UTC Unix seconds
|
|
#[arg(long = "consent-expires-at")]
|
|
pub consent_expires_at_unix: i64,
|
|
|
|
/// Artifact expiry as UTC Unix seconds
|
|
#[arg(long = "expires-at")]
|
|
pub expires_at_unix: i64,
|
|
|
|
/// Object transfer operation
|
|
#[arg(long, value_enum)]
|
|
pub operation: ConnectObjectPerformanceOperation,
|
|
|
|
/// Generated transfer size in bytes
|
|
#[arg(long = "traffic-bytes", default_value_t = 65_536)]
|
|
pub traffic_bytes: u64,
|
|
|
|
/// Maximum wall-clock duration in milliseconds
|
|
#[arg(long = "duration-millis", default_value_t = 1_000)]
|
|
pub duration_millis: u64,
|
|
|
|
/// Stable opaque alias for the deployment target
|
|
#[arg(long = "target-alias", default_value = "deployment-1", value_parser = NonEmptyStringValueParser::new())]
|
|
pub target_alias: String,
|
|
|
|
/// Confirm this explicit local L1 diagnostic operation
|
|
#[arg(long = "acknowledge-l1", required = true, action = clap::ArgAction::SetTrue)]
|
|
pub acknowledge_l1: bool,
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectDrivePerformanceOpts {
|
|
/// Directory containing an enrolled Connect device identity
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
|
|
/// Existing approved directory in which to create task-owned scratch state
|
|
#[arg(long = "scratch-dir")]
|
|
pub scratch_dir: PathBuf,
|
|
|
|
/// New local archive path; an existing file is never replaced
|
|
#[arg(long)]
|
|
pub output: PathBuf,
|
|
|
|
/// Negotiated producer schema version
|
|
#[arg(long = "schema-version", default_value_t = 1)]
|
|
pub schema_version: u16,
|
|
|
|
/// Negotiated producer capability
|
|
#[arg(long, default_value = "performance.drive@1", value_parser = NonEmptyStringValueParser::new())]
|
|
pub capability: String,
|
|
|
|
/// Organization resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub organization: String,
|
|
|
|
/// Cluster resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub cluster: String,
|
|
|
|
/// Cluster-device resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub device: String,
|
|
|
|
/// UUIDv7 diagnostic run identifier issued by Connect
|
|
#[arg(long = "run-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub run_uid: String,
|
|
|
|
/// UUIDv7 artifact identifier issued by Connect
|
|
#[arg(long = "artifact-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub artifact_uid: String,
|
|
|
|
/// UUIDv7 consent identifier issued by Connect
|
|
#[arg(long = "consent-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub consent_uid: String,
|
|
|
|
/// Consent policy revision bound to this measurement
|
|
#[arg(long = "policy-revision")]
|
|
pub policy_revision: u64,
|
|
|
|
/// Consent expiry as UTC Unix seconds
|
|
#[arg(long = "consent-expires-at")]
|
|
pub consent_expires_at_unix: i64,
|
|
|
|
/// Artifact expiry as UTC Unix seconds
|
|
#[arg(long = "expires-at")]
|
|
pub expires_at_unix: i64,
|
|
|
|
/// Maximum wall-clock duration in milliseconds
|
|
#[arg(long = "duration-millis", default_value_t = 1_000)]
|
|
pub duration_millis: u64,
|
|
|
|
/// Generated scratch payload size in bytes
|
|
#[arg(long = "scratch-bytes", default_value_t = 32_768)]
|
|
pub scratch_bytes: u64,
|
|
|
|
/// Individual read/write block size in bytes
|
|
#[arg(long = "block-bytes", default_value_t = 4_096)]
|
|
pub block_bytes: u64,
|
|
|
|
/// Confirm this explicit local diagnostic operation
|
|
#[arg(long = "acknowledge-l1", required = true, action = clap::ArgAction::SetTrue)]
|
|
pub acknowledge_l1: bool,
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectTelemetryOpts {
|
|
#[command(subcommand)]
|
|
pub command: ConnectTelemetryCommands,
|
|
}
|
|
|
|
#[derive(Subcommand, Clone)]
|
|
pub enum ConnectTelemetryCommands {
|
|
/// Capture process-local telemetry when an approved typed source is available
|
|
Record(ConnectTelemetryRecordOpts),
|
|
/// Forward an OTLP protobuf batch from stdin to a customer-approved collector
|
|
Otlp(ConnectTelemetryOtlpOpts),
|
|
/// Replay reviewed trace JSON read from stdin and write a signed export
|
|
Replay(ConnectTelemetryReplayOpts),
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectTelemetryArtifactOpts {
|
|
/// Directory containing an enrolled Connect device identity
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
|
|
/// New local archive path; an existing file is never replaced
|
|
#[arg(long)]
|
|
pub output: PathBuf,
|
|
|
|
/// Organization resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub organization: String,
|
|
|
|
/// Cluster resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub cluster: String,
|
|
|
|
/// Cluster-device resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub device: String,
|
|
|
|
/// UUIDv7 diagnostic run identifier issued by Connect
|
|
#[arg(long = "run-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub run_uid: String,
|
|
|
|
/// UUIDv7 artifact identifier issued by Connect
|
|
#[arg(long = "artifact-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub artifact_uid: String,
|
|
|
|
/// UUIDv7 consent identifier issued by Connect
|
|
#[arg(long = "consent-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub consent_uid: String,
|
|
|
|
/// Consent policy revision bound to this operation
|
|
#[arg(long = "policy-revision")]
|
|
pub policy_revision: u64,
|
|
|
|
/// Consent expiry as UTC Unix seconds
|
|
#[arg(long = "consent-expires-at")]
|
|
pub consent_expires_at_unix: i64,
|
|
|
|
/// Artifact expiry as UTC Unix seconds
|
|
#[arg(long = "expires-at")]
|
|
pub expires_at_unix: i64,
|
|
|
|
/// Confirm this explicit local L3 telemetry operation
|
|
#[arg(long = "acknowledge-l3", required = true, action = clap::ArgAction::SetTrue)]
|
|
pub acknowledge_l3: bool,
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectTelemetryRecordOpts {
|
|
#[command(flatten)]
|
|
pub artifact: ConnectTelemetryArtifactOpts,
|
|
|
|
/// Capture duration in milliseconds
|
|
#[arg(long = "duration-millis")]
|
|
pub duration_millis: u64,
|
|
|
|
/// Maximum exported span count
|
|
#[arg(long = "max-spans", default_value_t = 1_024)]
|
|
pub max_spans: usize,
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectTelemetryOtlpOpts {
|
|
#[command(flatten)]
|
|
pub artifact: ConnectTelemetryArtifactOpts,
|
|
|
|
/// Customer-approved OTLP/HTTP traces endpoint
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub endpoint: String,
|
|
|
|
/// Forward timeout in milliseconds
|
|
#[arg(long = "timeout-millis")]
|
|
pub timeout_millis: u64,
|
|
|
|
/// Environment variable containing the local Authorization header value
|
|
#[arg(long = "authorization-env", value_parser = NonEmptyStringValueParser::new())]
|
|
pub authorization_env: Option<String>,
|
|
}
|
|
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectTelemetryReplayOpts {
|
|
#[command(flatten)]
|
|
pub artifact: ConnectTelemetryArtifactOpts,
|
|
}
|
|
|
|
/// `connect logs` options.
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectLogsOpts {
|
|
/// Directory containing an enrolled Connect device identity
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
|
|
/// New local archive path; an existing file is never replaced
|
|
#[arg(long)]
|
|
pub output: PathBuf,
|
|
|
|
/// Capture the recent configured log window or tail new events
|
|
#[arg(long, value_enum, default_value = "batch")]
|
|
pub mode: ConnectLogsMode,
|
|
|
|
/// Negotiated producer schema version
|
|
#[arg(long = "schema-version", default_value_t = 1)]
|
|
pub schema_version: u16,
|
|
|
|
/// Negotiated producer capability
|
|
#[arg(long, default_value = "logs.capture@1")]
|
|
pub capability: String,
|
|
|
|
/// Organization resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub organization: String,
|
|
|
|
/// Cluster resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub cluster: String,
|
|
|
|
/// Cluster-device resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub device: String,
|
|
|
|
/// UUIDv7 diagnostic run identifier issued by Connect
|
|
#[arg(long = "run-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub run_uid: String,
|
|
|
|
/// UUIDv7 artifact identifier issued by Connect
|
|
#[arg(long = "artifact-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub artifact_uid: String,
|
|
|
|
/// UUIDv7 consent identifier issued by Connect
|
|
#[arg(long = "consent-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub consent_uid: String,
|
|
|
|
/// Consent policy revision bound to this capture
|
|
#[arg(long = "policy-revision")]
|
|
pub policy_revision: u64,
|
|
|
|
/// Consent expiry as UTC Unix seconds
|
|
#[arg(long = "consent-expires-at")]
|
|
pub consent_expires_at_unix: i64,
|
|
|
|
/// Artifact expiry as UTC Unix seconds
|
|
#[arg(long = "expires-at")]
|
|
pub expires_at_unix: i64,
|
|
|
|
/// Batch lookback or live capture duration in milliseconds
|
|
#[arg(long = "duration-millis")]
|
|
pub duration_millis: u64,
|
|
|
|
/// Maximum exported event count
|
|
#[arg(long = "max-events", default_value_t = 1_024)]
|
|
pub max_events: usize,
|
|
|
|
/// Confirm this explicit local L3 log capture
|
|
#[arg(long = "acknowledge-l3", required = true, action = clap::ArgAction::SetTrue)]
|
|
pub acknowledge_l3: bool,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
|
|
pub enum ConnectLogsMode {
|
|
Batch,
|
|
Live,
|
|
}
|
|
|
|
/// `connect profile` options.
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectProfileOpts {
|
|
/// Directory containing an enrolled Connect device identity
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
|
|
/// New local archive path; an existing file is never replaced
|
|
#[arg(long)]
|
|
pub output: PathBuf,
|
|
|
|
/// Profile producer to run
|
|
#[arg(long, value_enum)]
|
|
pub tool: ConnectProfileTool,
|
|
|
|
/// Thread source required by the threads producer
|
|
#[arg(long = "thread-scope", value_enum)]
|
|
pub thread_scope: Option<ConnectThreadProfileScope>,
|
|
|
|
/// Negotiated producer schema version
|
|
#[arg(long = "schema-version", default_value_t = 1)]
|
|
pub schema_version: u16,
|
|
|
|
/// Negotiated producer capability, such as profile.memory@1
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub capability: String,
|
|
|
|
/// Organization resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub organization: String,
|
|
|
|
/// Cluster resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub cluster: String,
|
|
|
|
/// Cluster-device resource name bound to the export
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub device: String,
|
|
|
|
/// UUIDv7 diagnostic run identifier issued by Connect
|
|
#[arg(long = "run-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub run_uid: String,
|
|
|
|
/// UUIDv7 artifact identifier issued by Connect
|
|
#[arg(long = "artifact-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub artifact_uid: String,
|
|
|
|
/// UUIDv7 consent identifier issued by Connect
|
|
#[arg(long = "consent-uid", value_parser = NonEmptyStringValueParser::new())]
|
|
pub consent_uid: String,
|
|
|
|
/// Consent policy revision bound to this capture
|
|
#[arg(long = "policy-revision")]
|
|
pub policy_revision: u64,
|
|
|
|
/// Consent expiry as UTC Unix seconds
|
|
#[arg(long = "consent-expires-at")]
|
|
pub consent_expires_at_unix: i64,
|
|
|
|
/// Artifact expiry as UTC Unix seconds
|
|
#[arg(long = "expires-at")]
|
|
pub expires_at_unix: i64,
|
|
|
|
/// Maximum capture duration in milliseconds
|
|
#[arg(long = "duration-millis")]
|
|
pub duration_millis: u64,
|
|
|
|
/// Sampling interval in microseconds
|
|
#[arg(long = "sample-period-micros")]
|
|
pub sample_period_micros: u64,
|
|
|
|
/// Confirm this explicit local L3 profile capture
|
|
#[arg(long = "acknowledge-l3", required = true, action = clap::ArgAction::SetTrue)]
|
|
pub acknowledge_l3: bool,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
|
|
pub enum ConnectProfileTool {
|
|
Cpu,
|
|
Memory,
|
|
Threads,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
|
|
pub enum ConnectThreadProfileScope {
|
|
TokioRuntime,
|
|
NativeThreads,
|
|
}
|
|
|
|
/// `connect register` options
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectRegisterOpts {
|
|
/// Connect agent API HTTPS base URL
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub endpoint: String,
|
|
|
|
/// PEM root CA file used only for this Connect endpoint
|
|
#[arg(long = "ca-file")]
|
|
pub ca_file: PathBuf,
|
|
|
|
/// Explicit directory shared with the Connect heartbeat runtime
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
|
|
/// Owner-only regular token file; omit to read the token from stdin
|
|
#[arg(long = "token-file")]
|
|
pub token_file: Option<PathBuf>,
|
|
}
|
|
|
|
/// Signed Connect service-license operations.
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectLicenseOpts {
|
|
#[command(subcommand)]
|
|
pub command: ConnectLicenseCommands,
|
|
}
|
|
|
|
/// Local service-license operations.
|
|
#[derive(Subcommand, Clone)]
|
|
pub enum ConnectLicenseCommands {
|
|
/// Verify and atomically install a downloaded or hand-carried license file
|
|
Import(ConnectLicenseArtifactOpts),
|
|
/// Verify a license file without changing installed state
|
|
Verify(ConnectLicenseArtifactOpts),
|
|
/// Verify and display the installed license for one deployment and service
|
|
Show(ConnectLicenseScopeOpts),
|
|
/// Check Connect and install an operator-approved replacement license
|
|
Renew(ConnectLicenseRenewOpts),
|
|
}
|
|
|
|
/// Online renewal transport plus local trust and scope pins.
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectLicenseRenewOpts {
|
|
/// HTTPS Connect agent API base URL
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub endpoint: String,
|
|
|
|
/// PEM root CA file used only for this Connect endpoint
|
|
#[arg(long = "ca-file")]
|
|
pub ca_file: PathBuf,
|
|
|
|
#[command(flatten)]
|
|
pub scope: ConnectLicenseScopeOpts,
|
|
}
|
|
|
|
/// Trust and scope pins shared by service-license commands.
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectLicenseScopeOpts {
|
|
/// Directory containing local service-license state
|
|
#[arg(long = "state-dir")]
|
|
pub state_dir: PathBuf,
|
|
|
|
/// File containing the pinned Ed25519 public key as canonical base64url
|
|
#[arg(long = "public-key-file")]
|
|
pub public_key_file: PathBuf,
|
|
|
|
/// SHA-256 key ID for the pinned public key
|
|
#[arg(long = "key-id", value_parser = NonEmptyStringValueParser::new())]
|
|
pub key_id: String,
|
|
|
|
/// Expected Connect license issuer
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub issuer: String,
|
|
|
|
/// Expected RustFS license audience
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub audience: String,
|
|
|
|
/// Expected organization resource name
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub organization: String,
|
|
|
|
/// Expected deployment resource name
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub deployment: String,
|
|
|
|
/// Expected service code
|
|
#[arg(long = "service-code", value_parser = NonEmptyStringValueParser::new())]
|
|
pub service_code: String,
|
|
}
|
|
|
|
/// A service-license file plus its local trust and scope pins.
|
|
#[derive(Args, Clone)]
|
|
pub struct ConnectLicenseArtifactOpts {
|
|
/// Downloaded or hand-carried signed license artifact
|
|
#[arg(long)]
|
|
pub artifact: PathBuf,
|
|
|
|
#[command(flatten)]
|
|
pub scope: ConnectLicenseScopeOpts,
|
|
}
|
|
|
|
/// Offline inspection subcommand options
|
|
#[derive(Args, Clone)]
|
|
pub struct InspectOpts {
|
|
#[command(subcommand)]
|
|
pub command: InspectCommands,
|
|
}
|
|
|
|
/// Offline inspection subcommands
|
|
#[derive(Subcommand, Clone)]
|
|
pub enum InspectCommands {
|
|
/// Export a bucket's persisted configuration bytes straight from drive roots
|
|
/// (works even when the config XML no longer parses)
|
|
BucketMeta(InspectBucketMetaOpts),
|
|
}
|
|
|
|
/// `inspect bucket-meta` options
|
|
#[derive(Args, Clone)]
|
|
#[command(
|
|
after_help = "IMPORTANT: Mount every source drive read-only for forensic use. Read-only application calls cannot prevent filesystem atime updates or path replacement races on writable mounts."
|
|
)]
|
|
pub struct InspectBucketMetaOpts {
|
|
/// Drive root path(s). Repeat for multi-drive nodes: erasure-coded metadata
|
|
/// needs enough drives for write quorum. Source media must be mounted
|
|
/// read-only for strict forensic use.
|
|
#[arg(long = "path", required = true, value_parser = NonEmptyStringValueParser::new())]
|
|
pub paths: Vec<String>,
|
|
|
|
/// Bucket whose metadata to inspect
|
|
#[arg(long, value_parser = NonEmptyStringValueParser::new())]
|
|
pub bucket: String,
|
|
|
|
/// Write the raw `.metadata.bin` blob and each stored config's exact bytes to --out
|
|
#[arg(long)]
|
|
pub raw: bool,
|
|
|
|
/// New output directory for --raw (default: ./bucket-meta-<bucket>)
|
|
#[arg(long, requires = "raw")]
|
|
pub out: Option<std::path::PathBuf>,
|
|
}
|
|
|
|
/// Diagnose report output format
|
|
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum DiagnoseFormat {
|
|
/// Terminal text report
|
|
Text,
|
|
/// Machine-readable JSON (stable schema_version)
|
|
Json,
|
|
/// Markdown, pasteable into a support ticket
|
|
Md,
|
|
}
|
|
|
|
/// Diagnose subcommand options
|
|
#[derive(Args, Clone)]
|
|
pub struct DiagnoseOpts {
|
|
/// Log inputs: files, directories, archives (.zip/.tar/.tar.gz/.zst/.gz), or "-" for stdin
|
|
#[arg(required = true)]
|
|
pub paths: Vec<String>,
|
|
|
|
/// Output format
|
|
#[arg(long, value_enum, default_value_t = DiagnoseFormat::Text)]
|
|
pub format: DiagnoseFormat,
|
|
|
|
/// Only consider events at/after this time (RFC-3339, or relative like "30m", "24h", "7d")
|
|
#[arg(long)]
|
|
pub since: Option<String>,
|
|
|
|
/// Only consider events at/before this time (same syntax as --since)
|
|
#[arg(long)]
|
|
pub until: Option<String>,
|
|
|
|
/// Minimum level to analyze (trace|debug|info|warn|error)
|
|
#[arg(long)]
|
|
pub min_level: Option<String>,
|
|
|
|
/// Hash bucket/object/key/IP values in the report (safe to forward)
|
|
#[arg(long)]
|
|
pub redact: bool,
|
|
|
|
/// Extra rules file (JSON; same-id rules override built-ins)
|
|
#[arg(long)]
|
|
pub rules: Option<std::path::PathBuf>,
|
|
|
|
/// Max unmatched error patterns to list
|
|
#[arg(long, default_value_t = 20)]
|
|
pub top: usize,
|
|
|
|
/// Max sample lines per finding
|
|
#[arg(long, default_value_t = 3)]
|
|
pub samples: usize,
|
|
}
|
|
|
|
/// Information type to display
|
|
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum InfoType {
|
|
/// System basic information (OS, architecture, hostname, etc.)
|
|
System,
|
|
/// Runtime information (PID, memory, CPU, threads, etc.)
|
|
Runtime,
|
|
/// Build information (version, build time, git info, etc.)
|
|
Build,
|
|
/// Current configuration information
|
|
Config,
|
|
/// Dependency library versions
|
|
Deps,
|
|
}
|
|
|
|
/// Info subcommand options
|
|
#[derive(Args, Clone)]
|
|
pub struct InfoOpts {
|
|
/// Display all information types
|
|
#[arg(long, conflicts_with = "info_type")]
|
|
pub all: bool,
|
|
|
|
/// Output in JSON format (default: markdown table)
|
|
#[arg(long)]
|
|
pub json: bool,
|
|
|
|
/// Type of information to display
|
|
#[arg(value_enum, conflicts_with = "all")]
|
|
pub info_type: Option<InfoType>,
|
|
}
|
|
|
|
/// TLS diagnostic subcommand options
|
|
#[derive(Args, Clone)]
|
|
pub struct TlsOpts {
|
|
#[command(subcommand)]
|
|
pub command: TlsCommands,
|
|
}
|
|
|
|
/// TLS diagnostic subcommands
|
|
#[derive(Subcommand, Clone)]
|
|
pub enum TlsCommands {
|
|
/// Inspect a TLS certificate directory
|
|
Inspect(TlsInspectOpts),
|
|
}
|
|
|
|
/// TLS inspect options
|
|
#[derive(Args, Clone)]
|
|
pub struct TlsInspectOpts {
|
|
/// TLS directory to inspect
|
|
#[arg(long = "path", alias = "tls-path", value_parser = NonEmptyStringValueParser::new())]
|
|
pub path: String,
|
|
}
|
|
|
|
/// Server subcommand options
|
|
#[derive(Args, Clone)]
|
|
#[command(after_help = "Allocator reclaim environment:
|
|
RUSTFS_ALLOCATOR_RECLAIM_ENABLED=true|false Enable allocator page reclaim after idle samples (default: true)
|
|
RUSTFS_ALLOCATOR_RECLAIM_INTERVAL_SECS=30 Sampling interval in seconds
|
|
RUSTFS_ALLOCATOR_RECLAIM_FORCE=true|false Request forceful collection when supported
|
|
RUSTFS_ALLOCATOR_RECLAIM_IDLE_INTERVALS=3 Consecutive idle samples required before reclaim")]
|
|
pub struct ServerOpts {
|
|
/// DIR points to a directory on a filesystem.
|
|
#[arg(
|
|
required = true,
|
|
env = "RUSTFS_VOLUMES",
|
|
value_delimiter = ' ',
|
|
value_parser = NonEmptyStringValueParser::new()
|
|
)]
|
|
pub volumes: Vec<String>,
|
|
|
|
/// bind to a specific ADDRESS:PORT, ADDRESS can be an IP or hostname
|
|
#[arg(
|
|
long,
|
|
default_value_t = rustfs_config::DEFAULT_ADDRESS.to_string(),
|
|
env = "RUSTFS_ADDRESS"
|
|
)]
|
|
pub address: String,
|
|
|
|
/// Domain name(s) for virtual-hosted-style S3 requests (comma-separated).
|
|
///
|
|
/// Required for clients that default to virtual-hosted-style addressing
|
|
/// (AWS SDK, Terraform/Pulumi, etc.), e.g. `RUSTFS_SERVER_DOMAINS=s3.example.com`
|
|
/// so that `bucket.s3.example.com` is routed to bucket `bucket`. When unset, only
|
|
/// path-style addressing is supported (configure clients with
|
|
/// `s3_use_path_style = true` / `force_path_style=true`).
|
|
#[arg(
|
|
long,
|
|
env = "RUSTFS_SERVER_DOMAINS",
|
|
value_delimiter = ',',
|
|
value_parser = NonEmptyStringValueParser::new()
|
|
)]
|
|
pub server_domains: Vec<String>,
|
|
|
|
/// Access key used for authentication.
|
|
#[arg(long, env = "RUSTFS_ACCESS_KEY", group = "access-key")]
|
|
pub access_key: Option<String>,
|
|
|
|
/// Access key stored in a file used for authentication.
|
|
#[arg(long, env = "RUSTFS_ACCESS_KEY_FILE", group = "access-key")]
|
|
pub access_key_file: Option<PathBuf>,
|
|
|
|
/// Secret key used for authentication.
|
|
#[arg(long, env = "RUSTFS_SECRET_KEY", group = "secret-key")]
|
|
pub secret_key: Option<String>,
|
|
|
|
/// Secret key stored in a file used for authentication.
|
|
#[arg(long, env = "RUSTFS_SECRET_KEY_FILE", group = "secret-key")]
|
|
pub secret_key_file: Option<PathBuf>,
|
|
|
|
/// Enable console server
|
|
#[arg(
|
|
long,
|
|
default_value_t = rustfs_config::DEFAULT_CONSOLE_ENABLE,
|
|
env = "RUSTFS_CONSOLE_ENABLE"
|
|
)]
|
|
pub console_enable: bool,
|
|
|
|
/// Console server bind address
|
|
#[arg(
|
|
long,
|
|
default_value_t = rustfs_config::DEFAULT_CONSOLE_ADDRESS.to_string(),
|
|
env = "RUSTFS_CONSOLE_ADDRESS"
|
|
)]
|
|
pub console_address: String,
|
|
|
|
/// Root OTLP endpoint for traces, metrics, and logs.
|
|
/// For the current observability pipeline this should be an OTLP/HTTP base
|
|
/// URL such as `http://otel-collector:4318` or
|
|
/// `http://host.docker.internal:4318`.
|
|
#[arg(
|
|
long,
|
|
default_value_t = rustfs_config::DEFAULT_OBS_ENDPOINT.to_string(),
|
|
env = "RUSTFS_OBS_ENDPOINT"
|
|
)]
|
|
pub obs_endpoint: String,
|
|
|
|
/// tls path for rustfs API and console.
|
|
#[arg(long, env = "RUSTFS_TLS_PATH")]
|
|
pub tls_path: Option<String>,
|
|
|
|
#[arg(long, env = "RUSTFS_LICENSE")]
|
|
pub license: Option<String>,
|
|
|
|
#[arg(long, env = "RUSTFS_REGION")]
|
|
pub region: Option<String>,
|
|
|
|
/// Enable KMS encryption for server-side encryption
|
|
#[arg(long, default_value_t = false, env = "RUSTFS_KMS_ENABLE")]
|
|
pub kms_enable: bool,
|
|
|
|
/// KMS backend type: local (development/testing only), vault or vault-kv2 (plain Vault KV v2 storage), vault-transit, static (development/testing only), aws
|
|
#[arg(long, default_value_t = rustfs_config::DEFAULT_KMS_BACKEND.to_string(), env = "RUSTFS_KMS_BACKEND")]
|
|
pub kms_backend: String,
|
|
|
|
/// KMS key directory for local backend
|
|
#[arg(long, env = "RUSTFS_KMS_KEY_DIR")]
|
|
pub kms_key_dir: Option<String>,
|
|
|
|
/// Master key for local KMS key-file encryption
|
|
#[arg(long, env = "RUSTFS_KMS_LOCAL_MASTER_KEY")]
|
|
pub kms_local_master_key: Option<String>,
|
|
|
|
/// Vault address for vault backend
|
|
#[arg(long, env = "RUSTFS_KMS_VAULT_ADDRESS")]
|
|
pub kms_vault_address: Option<String>,
|
|
|
|
/// Vault token for vault backend
|
|
#[arg(long, env = "RUSTFS_KMS_VAULT_TOKEN")]
|
|
pub kms_vault_token: Option<String>,
|
|
|
|
/// Vault mount path for vault or vault-transit backend
|
|
#[arg(long, env = "RUSTFS_KMS_VAULT_MOUNT_PATH")]
|
|
pub kms_vault_mount_path: Option<String>,
|
|
|
|
/// Default KMS key ID for encryption
|
|
#[arg(long, env = "RUSTFS_KMS_DEFAULT_KEY_ID")]
|
|
pub kms_default_key_id: Option<String>,
|
|
|
|
/// Allow development-only insecure KMS defaults
|
|
#[arg(long, default_value_t = false, env = "RUSTFS_KMS_ALLOW_INSECURE_DEV_DEFAULTS")]
|
|
pub kms_allow_insecure_dev_defaults: bool,
|
|
|
|
/// Disable adaptive buffer sizing with workload profiles
|
|
/// Set this flag to use legacy fixed-size buffer behavior from PR #869
|
|
#[arg(long, default_value_t = false, env = "RUSTFS_BUFFER_PROFILE_DISABLE")]
|
|
pub buffer_profile_disable: bool,
|
|
|
|
/// Workload profile for adaptive buffer sizing
|
|
/// Options: GeneralPurpose, AiTraining, DataAnalytics, WebWorkload, IndustrialIoT, SecureStorage
|
|
#[arg(long, default_value_t = rustfs_config::DEFAULT_BUFFER_PROFILE.to_string(), env = "RUSTFS_BUFFER_PROFILE")]
|
|
pub buffer_profile: String,
|
|
}
|
|
|
|
/// Result of parsing command line arguments
|
|
#[derive(Clone)]
|
|
pub enum CommandResult {
|
|
/// Server command with configuration
|
|
Server(Box<super::Config>),
|
|
/// Info command with options
|
|
Info(InfoOpts),
|
|
/// TLS command with options
|
|
Tls(TlsOpts),
|
|
/// Diagnose command with options
|
|
Diagnose(DiagnoseOpts),
|
|
/// Inspect command with options
|
|
Inspect(InspectOpts),
|
|
/// One-time Connect registration command
|
|
ConnectRegister(ConnectRegisterOpts),
|
|
/// Local Connect service-license command
|
|
ConnectLicense(ConnectLicenseCommands),
|
|
/// Explicit local Connect environment inventory command
|
|
ConnectEnvironmentInventory(ConnectEnvironmentInventoryOpts),
|
|
/// Consent-bound local Connect drive performance export
|
|
ConnectDrivePerformance(ConnectDrivePerformanceOpts),
|
|
/// Consent-bound client-to-deployment performance export
|
|
ConnectClientPerformance(ConnectClientPerformanceOpts),
|
|
/// Consent-bound S3 object performance export
|
|
ConnectObjectPerformance(ConnectObjectPerformanceOpts),
|
|
/// Consent-bound site-replication performance export
|
|
ConnectSiteReplicationPerformance(ConnectSiteReplicationPerformanceOpts),
|
|
/// Consent-bound local Connect profile export
|
|
ConnectProfile(ConnectProfileOpts),
|
|
/// Consent-bound local Connect log export
|
|
ConnectLogs(ConnectLogsOpts),
|
|
/// Consent-bound local Connect telemetry operation
|
|
ConnectTelemetry(ConnectTelemetryCommands),
|
|
/// Consent-bound local Connect top operation
|
|
ConnectTop(ConnectTopCommands),
|
|
}
|
|
|
|
/// Create default ServerOpts from environment variables
|
|
pub fn default_server_opts() -> ServerOpts {
|
|
ServerOpts {
|
|
volumes: std::env::var(ENV_RUSTFS_VOLUMES)
|
|
.unwrap_or_default()
|
|
.split(' ')
|
|
.filter(|s| !s.is_empty())
|
|
.map(|s| s.to_string())
|
|
.collect(),
|
|
address: DEFAULT_ADDRESS.to_string(),
|
|
server_domains: vec![],
|
|
access_key: None,
|
|
access_key_file: None,
|
|
secret_key: None,
|
|
secret_key_file: None,
|
|
console_enable: DEFAULT_CONSOLE_ENABLE,
|
|
console_address: DEFAULT_CONSOLE_ADDRESS.to_string(),
|
|
obs_endpoint: DEFAULT_OBS_ENDPOINT.to_string(),
|
|
tls_path: None,
|
|
license: None,
|
|
region: None,
|
|
kms_enable: false,
|
|
kms_backend: "local".to_string(),
|
|
kms_key_dir: None,
|
|
kms_local_master_key: None,
|
|
kms_vault_address: None,
|
|
kms_vault_token: None,
|
|
kms_vault_mount_path: None,
|
|
kms_default_key_id: None,
|
|
kms_allow_insecure_dev_defaults: false,
|
|
buffer_profile_disable: false,
|
|
buffer_profile: "GeneralPurpose".to_string(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{
|
|
Cli, Commands, ConnectCommands, ConnectInventoryCommands, ConnectLicenseCommands, InspectCommands,
|
|
preprocess_args_for_legacy,
|
|
};
|
|
use crate::version;
|
|
use clap::error::ErrorKind;
|
|
use clap::{CommandFactory, Parser};
|
|
|
|
#[test]
|
|
fn preprocess_help_command_displays_top_level_help() {
|
|
let args = preprocess_args_for_legacy(vec!["rustfs".to_string(), "help".to_string()]);
|
|
|
|
assert_eq!(args, vec!["rustfs".to_string(), "--help".to_string()]);
|
|
|
|
let err = match Cli::try_parse_from(args) {
|
|
Ok(_) => panic!("rustfs help should display help"),
|
|
Err(err) => err,
|
|
};
|
|
assert_eq!(err.kind(), ErrorKind::DisplayHelp);
|
|
}
|
|
|
|
#[test]
|
|
fn version_flags_use_display_version() {
|
|
let command = Cli::command();
|
|
|
|
let short = command.render_version();
|
|
assert!(short.contains(version::DISPLAY_VERSION));
|
|
assert!(!short.contains("build time"));
|
|
|
|
let long = command.render_long_version();
|
|
assert!(long.starts_with(&format!("rustfs {}\n", version::DISPLAY_VERSION)));
|
|
assert!(long.contains("build time"));
|
|
}
|
|
|
|
#[test]
|
|
fn inspect_bucket_meta_parses_repeated_drive_paths() {
|
|
let cli = Cli::try_parse_from([
|
|
"rustfs",
|
|
"inspect",
|
|
"bucket-meta",
|
|
"--path",
|
|
"/data/drive-1",
|
|
"--path",
|
|
"/data/drive-2",
|
|
"--bucket",
|
|
"example-bucket",
|
|
"--raw",
|
|
"--out",
|
|
"/tmp/export",
|
|
])
|
|
.expect("inspect arguments should parse");
|
|
|
|
let Some(Commands::Inspect(inspect)) = cli.command else {
|
|
panic!("inspect command expected");
|
|
};
|
|
let InspectCommands::BucketMeta(opts) = inspect.command;
|
|
assert_eq!(opts.paths, ["/data/drive-1", "/data/drive-2"]);
|
|
assert_eq!(opts.bucket, "example-bucket");
|
|
assert!(opts.raw);
|
|
assert_eq!(opts.out.as_deref(), Some(std::path::Path::new("/tmp/export")));
|
|
}
|
|
|
|
#[test]
|
|
fn inspect_bucket_meta_rejects_out_without_raw() {
|
|
let err = match Cli::try_parse_from([
|
|
"rustfs",
|
|
"inspect",
|
|
"bucket-meta",
|
|
"--path",
|
|
"/data/drive-1",
|
|
"--bucket",
|
|
"example-bucket",
|
|
"--out",
|
|
"/tmp/export",
|
|
]) {
|
|
Ok(_) => panic!("--out without --raw must be rejected"),
|
|
Err(err) => err,
|
|
};
|
|
|
|
assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
|
|
}
|
|
|
|
#[test]
|
|
fn connect_register_accepts_only_paths_and_endpoint_configuration() {
|
|
let cli = Cli::try_parse_from([
|
|
"rustfs",
|
|
"connect",
|
|
"register",
|
|
"--endpoint",
|
|
"https://connect.example/agent/",
|
|
"--ca-file",
|
|
"/etc/rustfs/connect-ca.pem",
|
|
"--state-dir",
|
|
"/var/lib/rustfs/connect",
|
|
])
|
|
.expect("connect register arguments should parse");
|
|
|
|
let Some(Commands::Connect(connect)) = cli.command else {
|
|
panic!("connect command expected");
|
|
};
|
|
let ConnectCommands::Register(register) = connect.command else {
|
|
panic!("connect register command expected");
|
|
};
|
|
assert_eq!(register.endpoint, "https://connect.example/agent/");
|
|
assert_eq!(register.ca_file, std::path::Path::new("/etc/rustfs/connect-ca.pem"));
|
|
assert_eq!(register.state_dir, std::path::Path::new("/var/lib/rustfs/connect"));
|
|
assert!(register.token_file.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn connect_license_renew_requires_transport_and_scope_pins() {
|
|
let cli = Cli::try_parse_from([
|
|
"rustfs",
|
|
"connect",
|
|
"license",
|
|
"renew",
|
|
"--endpoint",
|
|
"https://connect.example/agent/",
|
|
"--ca-file",
|
|
"/etc/rustfs/connect-ca.pem",
|
|
"--state-dir",
|
|
"/var/lib/rustfs/connect",
|
|
"--public-key-file",
|
|
"/etc/rustfs/connect-license.pub",
|
|
"--key-id",
|
|
"aabbcc",
|
|
"--issuer",
|
|
"connect",
|
|
"--audience",
|
|
"rustfs",
|
|
"--organization",
|
|
"organizations/018cc251-f400-7000-8000-000000000003",
|
|
"--deployment",
|
|
"organizations/018cc251-f400-7000-8000-000000000003/clusters/018cc251-f400-7000-8000-000000000004",
|
|
"--service-code",
|
|
"SUPPORT",
|
|
])
|
|
.expect("connect license renew arguments should parse");
|
|
|
|
let Some(Commands::Connect(connect)) = cli.command else {
|
|
panic!("connect command expected");
|
|
};
|
|
let ConnectCommands::License(license) = connect.command else {
|
|
panic!("connect license command expected");
|
|
};
|
|
let ConnectLicenseCommands::Renew(renew) = license.command else {
|
|
panic!("connect license renew command expected");
|
|
};
|
|
assert_eq!(renew.endpoint, "https://connect.example/agent/");
|
|
assert_eq!(renew.scope.service_code, "SUPPORT");
|
|
}
|
|
|
|
#[test]
|
|
fn connect_register_has_no_token_value_or_environment_option() {
|
|
for forbidden in ["--token", "--registration-token", "--token-env"] {
|
|
let result = Cli::try_parse_from([
|
|
"rustfs",
|
|
"connect",
|
|
"register",
|
|
"--endpoint",
|
|
"https://connect.example/agent/",
|
|
"--ca-file",
|
|
"/etc/rustfs/connect-ca.pem",
|
|
"--state-dir",
|
|
"/var/lib/rustfs/connect",
|
|
forbidden,
|
|
"secret",
|
|
]);
|
|
let Err(error) = result else {
|
|
panic!("secret-bearing command-line options must be rejected");
|
|
};
|
|
assert_eq!(error.kind(), ErrorKind::UnknownArgument);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn connect_register_help_states_the_unix_only_security_scope() {
|
|
let result = Cli::try_parse_from(["rustfs", "connect", "register", "--help"]);
|
|
let Err(help) = result else {
|
|
panic!("help exits without running registration");
|
|
};
|
|
|
|
assert_eq!(help.kind(), ErrorKind::DisplayHelp);
|
|
assert!(help.to_string().contains("Unix only"));
|
|
}
|
|
|
|
#[test]
|
|
fn connect_environment_inventory_requires_explicit_l1_acknowledgement() {
|
|
let error = Cli::try_parse_from([
|
|
"rustfs",
|
|
"connect",
|
|
"inventory",
|
|
"environment",
|
|
"--state-dir",
|
|
"/var/lib/rustfs/connect",
|
|
])
|
|
.err()
|
|
.expect("unacknowledged L1 inventory must fail");
|
|
assert_eq!(error.kind(), ErrorKind::MissingRequiredArgument);
|
|
assert!(error.to_string().contains("--acknowledge-l1"));
|
|
|
|
let cli = Cli::try_parse_from([
|
|
"rustfs",
|
|
"connect",
|
|
"inventory",
|
|
"environment",
|
|
"--state-dir",
|
|
"/var/lib/rustfs/connect",
|
|
"--acknowledge-l1",
|
|
])
|
|
.expect("acknowledged environment inventory parses");
|
|
let Some(Commands::Connect(connect)) = cli.command else {
|
|
panic!("connect command expected");
|
|
};
|
|
let ConnectCommands::Inventory(inventory) = connect.command else {
|
|
panic!("inventory command expected");
|
|
};
|
|
let ConnectInventoryCommands::Environment(environment) = inventory.command;
|
|
assert_eq!(environment.schema_version, 1);
|
|
assert_eq!(environment.capability, "inventory.environment@1");
|
|
assert_eq!(environment.timeout_seconds, 30);
|
|
assert!(environment.acknowledge_l1);
|
|
}
|
|
|
|
#[test]
|
|
fn connect_profile_requires_explicit_l3_acknowledgement() {
|
|
let arguments = [
|
|
"rustfs",
|
|
"connect",
|
|
"profile",
|
|
"--state-dir",
|
|
"/var/lib/rustfs/connect",
|
|
"--output",
|
|
"/tmp/profile.zip",
|
|
"--tool",
|
|
"memory",
|
|
"--capability",
|
|
"profile.memory@1",
|
|
"--organization",
|
|
"organizations/019e3ae0-0000-7000-8000-000000000001",
|
|
"--cluster",
|
|
"organizations/019e3ae0-0000-7000-8000-000000000001/clusters/019e3ae0-0000-7000-8000-000000000002",
|
|
"--device",
|
|
"organizations/019e3ae0-0000-7000-8000-000000000001/clusters/019e3ae0-0000-7000-8000-000000000002/clusterDevices/019e3ae0-0000-7000-8000-000000000003",
|
|
"--run-uid",
|
|
"019e3ae0-0000-7000-8000-000000000004",
|
|
"--artifact-uid",
|
|
"019e3ae0-0000-7000-8000-000000000005",
|
|
"--consent-uid",
|
|
"019e3ae0-0000-7000-8000-000000000006",
|
|
"--policy-revision",
|
|
"1",
|
|
"--consent-expires-at",
|
|
"4102444800",
|
|
"--expires-at",
|
|
"4102444700",
|
|
"--duration-millis",
|
|
"10",
|
|
"--sample-period-micros",
|
|
"1000",
|
|
];
|
|
let error = Cli::try_parse_from(arguments)
|
|
.err()
|
|
.expect("an incomplete unacknowledged profile must fail");
|
|
assert_eq!(error.kind(), ErrorKind::MissingRequiredArgument);
|
|
assert!(error.to_string().contains("--acknowledge-l3"));
|
|
}
|
|
|
|
#[test]
|
|
fn connect_logs_requires_explicit_l3_acknowledgement() {
|
|
let arguments = [
|
|
"rustfs",
|
|
"connect",
|
|
"logs",
|
|
"--state-dir",
|
|
"/var/lib/rustfs/connect",
|
|
"--output",
|
|
"/tmp/logs.zip",
|
|
"--organization",
|
|
"organizations/019e3ae0-0000-7000-8000-000000000001",
|
|
"--cluster",
|
|
"organizations/019e3ae0-0000-7000-8000-000000000001/clusters/019e3ae0-0000-7000-8000-000000000002",
|
|
"--device",
|
|
"organizations/019e3ae0-0000-7000-8000-000000000001/clusters/019e3ae0-0000-7000-8000-000000000002/clusterDevices/019e3ae0-0000-7000-8000-000000000003",
|
|
"--run-uid",
|
|
"019e3ae0-0000-7000-8000-000000000004",
|
|
"--artifact-uid",
|
|
"019e3ae0-0000-7000-8000-000000000005",
|
|
"--consent-uid",
|
|
"019e3ae0-0000-7000-8000-000000000006",
|
|
"--policy-revision",
|
|
"1",
|
|
"--consent-expires-at",
|
|
"4102444800",
|
|
"--expires-at",
|
|
"4102444700",
|
|
"--duration-millis",
|
|
"1000",
|
|
];
|
|
let error = Cli::try_parse_from(arguments)
|
|
.err()
|
|
.expect("unacknowledged log capture must fail");
|
|
assert_eq!(error.kind(), ErrorKind::MissingRequiredArgument);
|
|
assert!(error.to_string().contains("--acknowledge-l3"));
|
|
}
|
|
|
|
#[test]
|
|
fn connect_telemetry_record_requires_explicit_l3_acknowledgement() {
|
|
let arguments = [
|
|
"rustfs",
|
|
"connect",
|
|
"telemetry",
|
|
"record",
|
|
"--state-dir",
|
|
"/var/lib/rustfs/connect",
|
|
"--output",
|
|
"/tmp/telemetry.zip",
|
|
"--organization",
|
|
"organizations/019e3ae0-0000-7000-8000-000000000001",
|
|
"--cluster",
|
|
"organizations/019e3ae0-0000-7000-8000-000000000001/clusters/019e3ae0-0000-7000-8000-000000000002",
|
|
"--device",
|
|
"organizations/019e3ae0-0000-7000-8000-000000000001/clusters/019e3ae0-0000-7000-8000-000000000002/clusterDevices/019e3ae0-0000-7000-8000-000000000003",
|
|
"--run-uid",
|
|
"019e3ae0-0000-7000-8000-000000000004",
|
|
"--artifact-uid",
|
|
"019e3ae0-0000-7000-8000-000000000005",
|
|
"--consent-uid",
|
|
"019e3ae0-0000-7000-8000-000000000006",
|
|
"--policy-revision",
|
|
"1",
|
|
"--consent-expires-at",
|
|
"4102444800",
|
|
"--expires-at",
|
|
"4102444700",
|
|
"--duration-millis",
|
|
"10",
|
|
];
|
|
let error = Cli::try_parse_from(arguments)
|
|
.err()
|
|
.expect("unacknowledged telemetry capture must fail");
|
|
assert_eq!(error.kind(), ErrorKind::MissingRequiredArgument);
|
|
assert!(error.to_string().contains("--acknowledge-l3"));
|
|
}
|
|
|
|
#[test]
|
|
fn server_help_lists_allocator_reclaim_environment() {
|
|
let result = Cli::try_parse_from(["rustfs", "server", "--help"]);
|
|
let Err(help) = result else {
|
|
panic!("help exits without parsing server options");
|
|
};
|
|
|
|
assert_eq!(help.kind(), ErrorKind::DisplayHelp);
|
|
let help = help.to_string();
|
|
for env in [
|
|
"RUSTFS_ALLOCATOR_RECLAIM_ENABLED",
|
|
"RUSTFS_ALLOCATOR_RECLAIM_INTERVAL_SECS",
|
|
"RUSTFS_ALLOCATOR_RECLAIM_FORCE",
|
|
"RUSTFS_ALLOCATOR_RECLAIM_IDLE_INTERVALS",
|
|
] {
|
|
assert!(help.contains(env), "server help should mention {env}");
|
|
}
|
|
}
|
|
}
|