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

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

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"apis/jira-api",
"apis/vmapi-api",
"cli/bugview-cli",
"cli/mdata-client",
"cli/triton-cli",
"cli/vmapi-cli",
"clients/internal/bugview-client",
Expand Down Expand Up @@ -59,12 +60,16 @@ expect_used = "deny"
[workspace.dependencies]
askama = "0.15"
anyhow = "1.0"
base64 = "0.22"
build-data = "0.2"
camino = "1.1"
cargo_toml = "0.22"
chrono = "0.4"
crc32fast = "1.4"
clap = { version = "4.5", features = ["derive"] }
getrandom = "0.3"
dropshot = { version = "0.16" }
libc = "0.2"
dropshot-api-manager = "0.3"
dropshot-api-manager-types = "0.3"
openapiv3 = "2.0"
Expand Down
42 changes: 42 additions & 0 deletions cli/mdata-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
#
# Copyright 2026 Edgecast Cloud LLC.

[package]
name = "mdata-client"
version = "0.1.0"
edition.workspace = true
description = "Rust implementation of the SmartOS metadata client (mdata-get/put/list/delete)"

[lints]
workspace = true

[[bin]]
name = "mdata-get"
path = "src/bin/mdata_get.rs"

[[bin]]
name = "mdata-put"
path = "src/bin/mdata_put.rs"

[[bin]]
name = "mdata-list"
path = "src/bin/mdata_list.rs"

[[bin]]
name = "mdata-delete"
path = "src/bin/mdata_delete.rs"

[dependencies]
anyhow = { workspace = true }
base64 = { workspace = true }
crc32fast = { workspace = true }
getrandom = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }

[target.'cfg(unix)'.dependencies]
libc = { workspace = true }
50 changes: 50 additions & 0 deletions cli/mdata-client/src/bin/mdata_delete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright 2026 Edgecast Cloud LLC.

//! mdata-delete: Delete a metadata key.
//!
//! Usage: mdata-delete <keyname>
//!
//! Deleting a non-existent key is not considered an error.
//! Requires V2 protocol support from the metadata service.
//!
//! Exit codes:
//! 0 - Success (or key did not exist)
//! 2 - Error
//! 3 - Usage error

use mdata_client::protocol::Protocol;
use mdata_client::{Response, exit_code};

fn main() {
mdata_client::init_logging();
match run() {
Ok(code) => std::process::exit(code),
Err(e) => {
eprintln!("ERROR: {e}");
std::process::exit(exit_code::ERROR);
}
}
}

fn run() -> anyhow::Result<i32> {
let args: Vec<String> = std::env::args().collect();
if args.len() != 2 {
eprintln!(
"Usage: {} <keyname>",
args.first().map(String::as_str).unwrap_or("mdata-delete"),
);
return Ok(exit_code::USAGE_ERROR);
}

let key = &args[1];
let mut proto = Protocol::init()?;

match proto.delete(key)? {
// DELETE of non-existent key is not an error
Response::Success(_) | Response::NotFound => Ok(exit_code::SUCCESS),
}
}
61 changes: 61 additions & 0 deletions cli/mdata-client/src/bin/mdata_get.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright 2026 Edgecast Cloud LLC.

//! mdata-get: Retrieve the value of a metadata key.
//!
//! Usage: mdata-get <keyname>
//!
//! Exit codes:
//! 0 - Success (value printed to stdout)
//! 1 - Key not found
//! 2 - Error
//! 3 - Usage error

use mdata_client::protocol::Protocol;
use mdata_client::{Command, Response, exit_code};

fn main() {
mdata_client::init_logging();
match run() {
Ok(code) => std::process::exit(code),
Err(e) => {
eprintln!("ERROR: {e}");
std::process::exit(exit_code::ERROR);
}
}
}

fn run() -> anyhow::Result<i32> {
let args: Vec<String> = std::env::args().collect();
if args.len() != 2 {
eprintln!(
"Usage: {} <keyname>",
args.first().map(String::as_str).unwrap_or("mdata-get"),
);
return Ok(exit_code::USAGE_ERROR);
}

let key = &args[1];
let mut proto = Protocol::init()?;

match proto.execute(Command::Get, Some(key))? {
Response::Success(Some(data)) => {
print!("{data}");
if !data.ends_with('\n') {
println!();
}
Ok(exit_code::SUCCESS)
}
Response::Success(None) => {
println!();
Ok(exit_code::SUCCESS)
}
Response::NotFound => {
eprintln!("No metadata for '{key}'");
Ok(exit_code::NOT_FOUND)
}
}
}
56 changes: 56 additions & 0 deletions cli/mdata-client/src/bin/mdata_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright 2026 Edgecast Cloud LLC.

//! mdata-list: List all available metadata keys.
//!
//! Usage: mdata-list
//!
//! Exit codes:
//! 0 - Success (keys printed to stdout, one per line)
//! 2 - Error
//! 3 - Usage error

use mdata_client::protocol::Protocol;
use mdata_client::{Command, Response, exit_code};

fn main() {
mdata_client::init_logging();
match run() {
Ok(code) => std::process::exit(code),
Err(e) => {
eprintln!("ERROR: {e}");
std::process::exit(exit_code::ERROR);
}
}
}

fn run() -> anyhow::Result<i32> {
let args: Vec<String> = std::env::args().collect();
if args.len() != 1 {
eprintln!(
"Usage: {}",
args.first().map(String::as_str).unwrap_or("mdata-list"),
);
return Ok(exit_code::USAGE_ERROR);
}

let mut proto = Protocol::init()?;

match proto.execute(Command::Keys, None)? {
Response::Success(Some(data)) => {
print!("{data}");
if !data.ends_with('\n') {
println!();
}
Ok(exit_code::SUCCESS)
}
Response::Success(None) => Ok(exit_code::SUCCESS),
Response::NotFound => {
eprintln!("ERROR: unexpected NOTFOUND response for KEYS");
Ok(exit_code::ERROR)
}
}
}
72 changes: 72 additions & 0 deletions cli/mdata-client/src/bin/mdata_put.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright 2026 Edgecast Cloud LLC.

//! mdata-put: Set the value of a metadata key.
//!
//! Usage: mdata-put <keyname> [<value>]
//!
//! If <value> is not provided, reads from stdin (only when stdin is
//! not a terminal).
//!
//! Requires V2 protocol support from the metadata service.
//!
//! Exit codes:
//! 0 - Success
//! 2 - Error
//! 3 - Usage error

use std::io::{IsTerminal, Read};

use mdata_client::protocol::Protocol;
use mdata_client::{Response, exit_code};

fn main() {
mdata_client::init_logging();
match run() {
Ok(code) => std::process::exit(code),
Err(e) => {
eprintln!("ERROR: {e}");
std::process::exit(exit_code::ERROR);
}
}
}

fn run() -> anyhow::Result<i32> {
let args: Vec<String> = std::env::args().collect();
let progname = args.first().map(String::as_str).unwrap_or("mdata-put");

if args.len() < 2 || args.len() > 3 {
eprintln!("Usage: {progname} <keyname> [<value>]");
return Ok(exit_code::USAGE_ERROR);
}

let key = &args[1];

// Get value from argument or stdin
let value = if args.len() == 3 {
args[2].clone()
} else if !std::io::stdin().is_terminal() {
let mut buf = String::new();
std::io::stdin().read_to_string(&mut buf)?;
buf
} else {
eprintln!(
"Usage: {progname} <keyname> [<value>]\n\
ERROR: either specify value as argument or pipe via stdin"
);
return Ok(exit_code::USAGE_ERROR);
};

let mut proto = Protocol::init()?;

match proto.put(key, &value)? {
Response::Success(_) => Ok(exit_code::SUCCESS),
Response::NotFound => {
eprintln!("ERROR: unexpected NOTFOUND response for PUT");
Ok(exit_code::ERROR)
}
}
}
Loading
Loading