diff --git a/src/agent/Cargo.lock b/src/agent/Cargo.lock index 2988b73b95..0b320adb25 100644 --- a/src/agent/Cargo.lock +++ b/src/agent/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03345e98af8f3d786b6d9f656ccfa6ac316d954e92bc4841f0bba20789d5fb5a" +checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" dependencies = [ "gimli", ] @@ -83,9 +83,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.59" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" +checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" dependencies = [ "addr2line", "cc", @@ -414,15 +414,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" +checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" [[package]] name = "heck" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" dependencies = [ "unicode-segmentation", ] @@ -871,9 +871,12 @@ dependencies = [ [[package]] name = "object" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" +checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386" +dependencies = [ + "memchr", +] [[package]] name = "oci" @@ -1138,6 +1141,10 @@ name = "protobuf" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e86d370532557ae7573551a1ec8235a0f8d6cb276c7c9e6aa490b511c447485" +dependencies = [ + "serde", + "serde_derive", +] [[package]] name = "protobuf-codegen" @@ -1164,6 +1171,8 @@ version = "0.1.0" dependencies = [ "async-trait", "protobuf", + "serde", + "serde_json", "ttrpc", "ttrpc-codegen", ] @@ -1304,9 +1313,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustjail" @@ -1364,18 +1373,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.129" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.129" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", @@ -1384,9 +1393,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.64" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "itoa", "ryu", @@ -1804,9 +1813,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-xid" diff --git a/src/agent/protocols/Cargo.toml b/src/agent/protocols/Cargo.toml index 447393646b..ae93e7fa19 100644 --- a/src/agent/protocols/Cargo.toml +++ b/src/agent/protocols/Cargo.toml @@ -4,10 +4,16 @@ version = "0.1.0" authors = ["The Kata Containers community "] edition = "2018" +[features] +default = [] +with-serde = [ "serde", "serde_json" ] + [dependencies] ttrpc = { version = "0.5.0", features = ["async"] } async-trait = "0.1.42" -protobuf = "=2.14.0" +protobuf = { version = "=2.14.0", features = ["with-serde"] } +serde = { version = "1.0.130", features = ["derive"], optional = true } +serde_json = { version = "1.0.68", optional = true } [build-dependencies] ttrpc-codegen = "0.2.0" diff --git a/src/agent/protocols/build.rs b/src/agent/protocols/build.rs index 4e0aa30522..39654882be 100644 --- a/src/agent/protocols/build.rs +++ b/src/agent/protocols/build.rs @@ -3,29 +3,148 @@ // SPDX-License-Identifier: Apache-2.0 // -use std::fs; -use ttrpc_codegen::{Codegen, Customize}; +use std::fs::File; +use std::io::{BufRead, BufReader, Read, Write}; +use std::path::Path; +use std::process::exit; +use ttrpc_codegen::{Codegen, Customize, ProtobufCustomize}; + +fn replace_text_in_file(file_name: &str, from: &str, to: &str) -> Result<(), std::io::Error> { + let mut src = File::open(file_name)?; + let mut contents = String::new(); + src.read_to_string(&mut contents).unwrap(); + drop(src); + + let new_contents = contents.replace(from, to); + + let mut dst = File::create(&file_name)?; + dst.write_all(new_contents.as_bytes())?; + + Ok(()) +} + +fn use_serde(protos: &[&str], out_dir: &Path) -> Result<(), std::io::Error> { + protos + .iter() + .try_for_each(|f: &&str| -> Result<(), std::io::Error> { + let out_file = Path::new(f) + .file_name() + .and_then(|s| s.to_str()) + .ok_or(format!("failed to get proto file name for {:?}", f)) + .map(|s| { + let t = s.replace(".proto", ".rs"); + out_dir.join(t) + }) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))? + .to_str() + .ok_or(format!("cannot convert {:?} path to string", f)) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))? + .to_string(); + + replace_text_in_file( + &out_file, + "derive(Serialize, Deserialize)", + "derive(serde::Serialize, serde::Deserialize)", + ) + }) +} + +fn handle_file(autogen_comment: &str, rust_filename: &str) -> Result<(), std::io::Error> { + let mut new_contents = Vec::new(); + + let file = File::open(rust_filename)?; + + let reader = BufReader::new(file); + + // Guard the code since it is only needed for the agent-ctl tool, + // not the agent itself. + let serde_default_code = r#"#[cfg_attr(feature = "with-serde", serde(default))]"#; + + for line in reader.lines() { + let line = line?; + + new_contents.push(line.clone()); + + let pattern = "//! Generated file from"; + + if line.starts_with(&pattern) { + new_contents.push(autogen_comment.into()); + } + + let struct_pattern = "pub struct "; + + // Although we've requested serde support via `Customize`, to + // allow the `kata-agent-ctl` tool to partially deserialise structures + // specified in JSON, we need this bit of additional magic. + if line.starts_with(&struct_pattern) { + new_contents.insert(new_contents.len() - 1, serde_default_code.trim().into()); + } + } + + let data = new_contents.join("\n"); + + let mut dst = File::create(&rust_filename)?; + + dst.write_all(data.as_bytes())?; + + Ok(()) +} + +fn real_main() -> Result<(), std::io::Error> { + let autogen_comment = format!("\n//! Generated by {:?} ({:?})", file!(), module_path!()); -fn main() { let protos = vec![ - "protos/types.proto", "protos/agent.proto", - "protos/health.proto", "protos/google/protobuf/empty.proto", + "protos/health.proto", "protos/oci.proto", + "protos/types.proto", ]; + // Tell Cargo that if the .proto files changed, to rerun this build script. + protos + .iter() + .for_each(|p| println!("cargo:rerun-if-changed={}", &p)); + + let ttrpc_options = Customize { + async_server: true, + ..Default::default() + }; + + let protobuf_options = ProtobufCustomize { + serde_derive: Some(true), + ..Default::default() + }; + + let out_dir = Path::new("src"); + Codegen::new() - .out_dir("src") + .out_dir(out_dir) .inputs(&protos) .include("protos") + .customize(ttrpc_options) .rust_protobuf() - .customize(Customize { - async_server: true, - ..Default::default() - }) - .run() - .expect("Gen codes failed."); + .rust_protobuf_customize(protobuf_options) + .run()?; + + for file in protos.iter() { + let proto_filename = Path::new(file).file_name().unwrap(); + + let generated_file = proto_filename + .to_str() + .ok_or("failed") + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))? + .replace(".proto", ".rs"); + + let out_file = out_dir.join(generated_file); + + let out_file_str = out_file + .to_str() + .ok_or("failed") + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + + handle_file(&autogen_comment, out_file_str)?; + } // There is a message named 'Box' in oci.proto // so there is a struct named 'Box', we should replace Box to ::std::boxed::Box @@ -34,11 +153,16 @@ fn main() { "src/oci.rs", "self: Box", "self: ::std::boxed::Box", - ) - .unwrap(); + )?; + + use_serde(&protos, out_dir)?; + + Ok(()) } -fn replace_text_in_file(file_name: &str, from: &str, to: &str) -> Result<(), std::io::Error> { - let new_contents = fs::read_to_string(file_name)?.replace(from, to); - fs::write(&file_name, new_contents.as_bytes()) +fn main() { + if let Err(e) = real_main() { + eprintln!("ERROR: {}", e); + exit(1); + } } diff --git a/tools/agent-ctl/Cargo.lock b/tools/agent-ctl/Cargo.lock index 4bcca80f9e..922d486260 100644 --- a/tools/agent-ctl/Cargo.lock +++ b/tools/agent-ctl/Cargo.lock @@ -818,6 +818,10 @@ name = "protobuf" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e86d370532557ae7573551a1ec8235a0f8d6cb276c7c9e6aa490b511c447485" +dependencies = [ + "serde", + "serde_derive", +] [[package]] name = "protobuf-codegen" @@ -844,6 +848,8 @@ version = "0.1.0" dependencies = [ "async-trait", "protobuf", + "serde", + "serde_json", "ttrpc", "ttrpc-codegen", ] @@ -1050,18 +1056,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.126" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.126" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", @@ -1070,9 +1076,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.64" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "itoa", "ryu", diff --git a/tools/agent-ctl/Cargo.toml b/tools/agent-ctl/Cargo.toml index 95e640edde..e6bd1727ae 100644 --- a/tools/agent-ctl/Cargo.toml +++ b/tools/agent-ctl/Cargo.toml @@ -10,7 +10,7 @@ authors = ["The Kata Containers community "] edition = "2018" [dependencies] -protocols = { path = "../../src/agent/protocols" } +protocols = { path = "../../src/agent/protocols", features = ["with-serde"] } rustjail = { path = "../../src/agent/rustjail" } oci = { path = "../../src/agent/oci" } @@ -20,6 +20,8 @@ anyhow = "1.0.31" hex = "0.4.2" byteorder = "1.3.4" +# Note: this crate sets the slog 'max_*' features which allows the log level +# to be modified at runtime. logging = { path = "../../pkg/logging" } slog = "2.5.2" slog-scope = "4.3.0" @@ -35,5 +37,5 @@ ttrpc = { version = "0.5.0" } humantime = "2.0.0" # For Options (state passing) -serde = { version = "1.0.110", features = ["derive"] } -serde_json = "1.0.53" +serde = { version = "1.0.130", features = ["derive"] } +serde_json = "1.0.68" diff --git a/tools/agent-ctl/README.md b/tools/agent-ctl/README.md index f4c42e508b..fa2caf99dc 100644 --- a/tools/agent-ctl/README.md +++ b/tools/agent-ctl/README.md @@ -60,6 +60,49 @@ $ mkdir -p "$rootfs_dir" && (cd "$bundle_dir" && runc spec) $ sudo docker export $(sudo docker create "$image") | tar -C "$rootfs_dir" -xvf - ``` + +### Specify API commands to run + +The tool allows one or more API commands to be specified using the `-c` or +`--cmd` command-line options. At their simplest, these are just the name of +the API commands, which will make the API command using default values +(generally blank or empty) where possible. However, some API calls require +some basic value to be specified such as a sandbox ID or container ID. For +these calls, the tool will generate a value by default unless told not to. + +If the user wishes to, they may specify these values as part of the command +using `name=value` syntax. + +In addition to this, it is possible to specify either a complete or partial +set of values for the API call using JSON syntax, either directly on the +command-line or via a file URI. + +The table below summarises the possible ways of specifying an API call to +make. + +| CLI values | API Query | +|-|-| +| `-c 'SomeAPIName' -n` | Calls the API using the default values for all request options | +| `-c 'SomeAPIName'` | Calls the API specifying some values automatically if possible | +| `-c 'SomeAPIName foo=bar baz="hello world" x=3 y="a cat"'` | Calls the API specifying various values in name/value form | +| `-c 'SomeAPIName json://{}' -n` | Calls the API specifying empty values via an empty JSON document | +| `-c 'SomeAPIName json://{"foo": true, "bar": "hello world"}' -n` | Calls the API specifying _some_ values in JSON syntax | +| `-c 'SomeAPIName file:///foo.json' -n` | Calls the API passing the JSON values from the specified file | + +#### JSON Example + +An example showing how to specify the messages fields for an API call +(`GetGuestDetails`): + +```sh +$ cargo run -- -l debug connect --server-address "unix://@/tmp/foo.socket" --bundle-dir "$bundle_dir" -c Check -c 'GetGuestDetails json://{"mem_block_size": true, "mem_hotplug_probe": true}' +``` + +> **Note:** +> +> For details of the names of the APIs to call and the available fields +> in each API, see the [Code Summary](#code-summary) section. + ### Connect to a real Kata Container The method used to connect to Kata Containers agent depends on the configured diff --git a/tools/agent-ctl/src/client.rs b/tools/agent-ctl/src/client.rs index 536fabce1a..ce88d10bb8 100644 --- a/tools/agent-ctl/src/client.rs +++ b/tools/agent-ctl/src/client.rs @@ -25,6 +25,23 @@ use std::time::Duration; use ttrpc; use ttrpc::context::Context; +// Run the specified closure to set an automatic value if the ttRPC Context +// does not contain the special values requesting automatic values be +// suppressed. +macro_rules! run_if_auto_values { + ($ctx:expr, $closure:expr) => {{ + let cfg = $ctx.metadata.get(METADATA_CFG_NS); + + if let Some(v) = cfg { + if v.contains(&NO_AUTO_VALUES_CFG_NAME.to_string()) { + debug!(sl!(), "Running closure to generate values"); + + $closure()?; + } + } + }}; +} + // Hack until the actual Context type supports this. fn clone_context(ctx: &Context) -> Context { Context { @@ -82,6 +99,13 @@ const DEFAULT_PROC_SIGNAL: &'static str = "SIGKILL"; const ERR_API_FAILED: &str = "API failed"; +// Value used as a "namespace" in the ttRPC Context's metadata. +const METADATA_CFG_NS: &str = "agent-ctl-cfg"; + +// Special value which if found means do not generate any values +// automatically. +const NO_AUTO_VALUES_CFG_NAME: &str = "no-auto-values"; + static AGENT_CMDS: &'static [AgentCmd] = &[ AgentCmd { name: "AddARPNeighbors", @@ -311,7 +335,7 @@ fn get_agent_cmd_details() -> Vec { fn get_agent_cmd_func(name: &str) -> Result { for cmd in AGENT_CMDS { - if cmd.name == name { + if cmd.name.eq(name) { return Ok(cmd.fp); } } @@ -339,7 +363,7 @@ fn get_all_cmd_details() -> Vec { fn get_builtin_cmd_func(name: &str) -> Result { for cmd in BUILTIN_CMDS { - if cmd.name == name { + if cmd.name.eq(name) { return Ok(cmd.fp); } } @@ -416,7 +440,7 @@ fn create_ttrpc_client( hybrid_vsock_port: u64, hybrid_vsock: bool, ) -> Result { - if server_address == "" { + if server_address.is_empty() { return Err(anyhow!("server address cannot be blank")); } @@ -553,7 +577,7 @@ fn announce(cfg: &Config) { } pub fn client(cfg: &Config, commands: Vec<&str>) -> Result<()> { - if commands.len() == 1 && commands[0] == "list" { + if commands.len() == 1 && commands[0].eq("list") { println!("Built-in commands:\n"); let mut builtin_cmds = get_builtin_cmd_details(); @@ -591,7 +615,16 @@ pub fn client(cfg: &Config, commands: Vec<&str>) -> Result<()> { let mut options = Options::new(); - let ttrpc_ctx = ttrpc::context::with_timeout(cfg.timeout_nano); + let mut ttrpc_ctx = ttrpc::context::with_timeout(cfg.timeout_nano); + + // Allow the commands to change their behaviour based on the value + // of this option. + + if !cfg.no_auto_values { + ttrpc_ctx.add(METADATA_CFG_NS.into(), NO_AUTO_VALUES_CFG_NAME.to_string()); + + debug!(sl!(), "Automatic value generation disabled"); + } // Special-case loading the OCI config file so it is accessible // to all commands. @@ -654,7 +687,7 @@ fn handle_cmd( let cmd = fields[0]; - if cmd == "" { + if cmd.is_empty() { // Ignore empty commands return (Ok(()), false); } @@ -754,7 +787,7 @@ fn handle_agent_cmd( return (result, false); } - let shutdown = cmd == SHUTDOWN_CMD; + let shutdown = cmd.eq(SHUTDOWN_CMD); (Ok(()), shutdown) } @@ -777,7 +810,7 @@ fn interactive_client_loop( let cmdline = readline("Enter command").map_err(|e| anyhow!(e).context("failed to read line"))?; - if cmdline == "" { + if cmdline.is_empty() { continue; } @@ -825,15 +858,12 @@ fn agent_cmd_health_check( _client: &AgentServiceClient, health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { - let mut req = CheckRequest::default(); + let req: CheckRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - // value unused - req.set_service("".to_string()); - debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); let reply = health @@ -851,16 +881,13 @@ fn agent_cmd_health_version( _client: &AgentServiceClient, health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { // XXX: Yes, the API is actually broken! - let mut req = CheckRequest::default(); + let req: CheckRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - // value unused - req.set_service("".to_string()); - debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); let reply = health @@ -880,12 +907,16 @@ fn agent_cmd_sandbox_create( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = CreateSandboxRequest::default(); + let mut req: CreateSandboxRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let sid = utils::get_option("sid", options, args); - req.set_sandbox_id(sid); + run_if_auto_values!(ctx, || -> Result<()> { + let sid = utils::get_option("sid", options, args)?; + req.set_sandbox_id(sid); + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -904,9 +935,9 @@ fn agent_cmd_sandbox_destroy( client: &AgentServiceClient, _health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { - let req = DestroySandboxRequest::default(); + let req: DestroySandboxRequest = utils::make_request(args)?; let ctx = clone_context(ctx); @@ -929,20 +960,23 @@ fn agent_cmd_container_create( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = CreateContainerRequest::default(); + let mut req: CreateContainerRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); - let exec_id = utils::get_option("exec_id", options, args); - // FIXME: container create: add back "spec=file:///" support - let ttrpc_spec = utils::get_ttrpc_spec(options, &cid).map_err(|e| anyhow!(e))?; + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + let exec_id = utils::get_option("exec_id", options, args)?; + let ttrpc_spec = utils::get_ttrpc_spec(options, &cid).map_err(|e| anyhow!(e))?; - req.set_container_id(cid); - req.set_exec_id(exec_id); - req.set_OCI(ttrpc_spec); + req.set_container_id(cid); + req.set_exec_id(exec_id); + req.set_OCI(ttrpc_spec); + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -963,13 +997,15 @@ fn agent_cmd_container_remove( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = RemoveContainerRequest::default(); - - let cid = utils::get_option("cid", options, args); + let mut req: RemoveContainerRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - req.set_container_id(cid); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + req.set_container_id(cid); + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -990,32 +1026,36 @@ fn agent_cmd_container_exec( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = ExecProcessRequest::default(); + let mut req: ExecProcessRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); - let exec_id = utils::get_option("exec_id", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + let exec_id = utils::get_option("exec_id", options, args)?; - let ttrpc_spec = utils::get_ttrpc_spec(options, &cid).map_err(|e| anyhow!(e))?; + let ttrpc_spec = utils::get_ttrpc_spec(options, &cid).map_err(|e| anyhow!(e))?; - let bundle_dir = options - .get("bundle-dir") - .ok_or("BUG: bundle-dir missing") - .map_err(|e| anyhow!(e))?; + let bundle_dir = options + .get("bundle-dir") + .ok_or("BUG: bundle-dir missing") + .map_err(|e| anyhow!(e))?; - let process = ttrpc_spec - .Process - .into_option() - .ok_or(format!( - "failed to get process from OCI spec: {}", - bundle_dir, - )) - .map_err(|e| anyhow!(e))?; + let process = ttrpc_spec + .Process + .into_option() + .ok_or(format!( + "failed to get process from OCI spec: {}", + bundle_dir, + )) + .map_err(|e| anyhow!(e))?; - req.set_container_id(cid); - req.set_exec_id(exec_id); - req.set_process(process); + req.set_container_id(cid); + req.set_exec_id(exec_id); + req.set_process(process); + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1036,13 +1076,16 @@ fn agent_cmd_container_stats( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = StatsContainerRequest::default(); + let mut req: StatsContainerRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; - req.set_container_id(cid); + req.set_container_id(cid); + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1063,13 +1106,16 @@ fn agent_cmd_container_pause( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = PauseContainerRequest::default(); + let mut req: PauseContainerRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; - req.set_container_id(cid); + req.set_container_id(cid); + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1090,13 +1136,16 @@ fn agent_cmd_container_resume( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = ResumeContainerRequest::default(); - - let cid = utils::get_option("cid", options, args); + let mut req: ResumeContainerRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - req.set_container_id(cid); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + + req.set_container_id(cid); + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1117,13 +1166,16 @@ fn agent_cmd_container_start( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = StartContainerRequest::default(); + let mut req: StartContainerRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; - req.set_container_id(cid); + req.set_container_id(cid); + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1142,13 +1194,18 @@ fn agent_cmd_sandbox_get_guest_details( client: &AgentServiceClient, _health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { - let mut req = GuestDetailsRequest::default(); + let mut req: GuestDetailsRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - req.set_mem_block_size(true); + run_if_auto_values!(ctx, || -> Result<()> { + req.set_mem_block_size(true); + req.set_mem_hotplug_probe(true); + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1169,15 +1226,19 @@ fn agent_cmd_container_wait_process( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = WaitProcessRequest::default(); + let mut req: WaitProcessRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); - let exec_id = utils::get_option("exec_id", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + let exec_id = utils::get_option("exec_id", options, args)?; - req.set_container_id(cid); - req.set_exec_id(exec_id); + req.set_container_id(cid); + req.set_exec_id(exec_id); + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1198,25 +1259,29 @@ fn agent_cmd_container_signal_process( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = SignalProcessRequest::default(); + let mut req: SignalProcessRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); - let exec_id = utils::get_option("exec_id", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + let exec_id = utils::get_option("exec_id", options, args)?; - let mut sigstr = utils::get_option("signal", options, args); + let mut sigstr = utils::get_option("signal", options, args)?; - // Convert to a numeric - if sigstr == "" { - sigstr = DEFAULT_PROC_SIGNAL.to_string(); - } + // Convert to a numeric + if sigstr.is_empty() { + sigstr = DEFAULT_PROC_SIGNAL.to_string(); + } - let signum = utils::signame_to_signum(&sigstr).map_err(|e| anyhow!(e))?; + let signum = utils::signame_to_signum(&sigstr).map_err(|e| anyhow!(e))?; - req.set_container_id(cid); - req.set_exec_id(exec_id); - req.set_signal(signum as u32); + req.set_container_id(cid); + req.set_exec_id(exec_id); + req.set_signal(signum as u32); + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1235,9 +1300,9 @@ fn agent_cmd_sandbox_update_interface( client: &AgentServiceClient, _health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { - let req = UpdateInterfaceRequest::default(); + let req: UpdateInterfaceRequest = utils::make_request(args)?; let ctx = clone_context(ctx); @@ -1260,9 +1325,9 @@ fn agent_cmd_sandbox_update_routes( client: &AgentServiceClient, _health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { - let req = UpdateRoutesRequest::default(); + let req: UpdateRoutesRequest = utils::make_request(args)?; let ctx = clone_context(ctx); @@ -1286,9 +1351,9 @@ fn agent_cmd_sandbox_list_interfaces( client: &AgentServiceClient, _health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { - let req = ListInterfacesRequest::default(); + let req: ListInterfacesRequest = utils::make_request(args)?; let ctx = clone_context(ctx); @@ -1309,9 +1374,9 @@ fn agent_cmd_sandbox_list_routes( client: &AgentServiceClient, _health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { - let req = ListRoutesRequest::default(); + let req: ListRoutesRequest = utils::make_request(args)?; let ctx = clone_context(ctx); @@ -1334,34 +1399,38 @@ fn agent_cmd_container_tty_win_resize( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = TtyWinResizeRequest::default(); + let mut req: TtyWinResizeRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); - let exec_id = utils::get_option("exec_id", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + let exec_id = utils::get_option("exec_id", options, args)?; - req.set_container_id(cid); - req.set_exec_id(exec_id); + req.set_container_id(cid); + req.set_exec_id(exec_id); - let rows_str = utils::get_option("row", options, args); + let rows_str = utils::get_option("row", options, args)?; - if rows_str != "" { - let rows = rows_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid row size"))?; - req.set_row(rows); - } + if rows_str != "" { + let rows = rows_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid row size"))?; + req.set_row(rows); + } - let cols_str = utils::get_option("column", options, args); + let cols_str = utils::get_option("column", options, args)?; - if cols_str != "" { - let cols = cols_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid column size"))?; + if cols_str != "" { + let cols = cols_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid column size"))?; - req.set_column(cols); - } + req.set_column(cols); + } + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1382,15 +1451,19 @@ fn agent_cmd_container_close_stdin( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = CloseStdinRequest::default(); + let mut req: CloseStdinRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); - let exec_id = utils::get_option("exec_id", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + let exec_id = utils::get_option("exec_id", options, args)?; - req.set_container_id(cid); - req.set_exec_id(exec_id); + req.set_container_id(cid); + req.set_exec_id(exec_id); + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1411,24 +1484,28 @@ fn agent_cmd_container_read_stdout( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = ReadStreamRequest::default(); + let mut req: ReadStreamRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); - let exec_id = utils::get_option("exec_id", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + let exec_id = utils::get_option("exec_id", options, args)?; - req.set_container_id(cid); - req.set_exec_id(exec_id); + req.set_container_id(cid); + req.set_exec_id(exec_id); - let length_str = utils::get_option("len", options, args); + let length_str = utils::get_option("len", options, args)?; - if length_str != "" { - let length = length_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid length"))?; - req.set_len(length); - } + if length_str != "" { + let length = length_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid length"))?; + req.set_len(length); + } + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1449,24 +1526,28 @@ fn agent_cmd_container_read_stderr( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = ReadStreamRequest::default(); + let mut req: ReadStreamRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); - let exec_id = utils::get_option("exec_id", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + let exec_id = utils::get_option("exec_id", options, args)?; - req.set_container_id(cid); - req.set_exec_id(exec_id); + req.set_container_id(cid); + req.set_exec_id(exec_id); - let length_str = utils::get_option("len", options, args); + let length_str = utils::get_option("len", options, args)?; - if length_str != "" { - let length = length_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid length"))?; - req.set_len(length); - } + if length_str != "" { + let length = length_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid length"))?; + req.set_len(length); + } + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1487,19 +1568,23 @@ fn agent_cmd_container_write_stdin( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = WriteStreamRequest::default(); + let mut req: WriteStreamRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); - let exec_id = utils::get_option("exec_id", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; + let exec_id = utils::get_option("exec_id", options, args)?; - let str_data = utils::get_option("data", options, args); - let data = utils::str_to_bytes(&str_data)?; + let str_data = utils::get_option("data", options, args)?; + let data = utils::str_to_bytes(&str_data)?; - req.set_container_id(cid); - req.set_exec_id(exec_id); - req.set_data(data.to_vec()); + req.set_container_id(cid); + req.set_exec_id(exec_id); + req.set_data(data.to_vec()); + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1518,9 +1603,9 @@ fn agent_cmd_sandbox_get_metrics( client: &AgentServiceClient, _health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { - let req = GetMetricsRequest::default(); + let req: GetMetricsRequest = utils::make_request(args)?; let ctx = clone_context(ctx); @@ -1541,9 +1626,9 @@ fn agent_cmd_sandbox_get_oom_event( client: &AgentServiceClient, _health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { - let req = GetOOMEventRequest::default(); + let req: GetOOMEventRequest = utils::make_request(args)?; let ctx = clone_context(ctx); @@ -1566,78 +1651,82 @@ fn agent_cmd_sandbox_copy_file( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = CopyFileRequest::default(); + let mut req: CopyFileRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let path = utils::get_option("path", options, args); - if path != "" { - req.set_path(path); - } + run_if_auto_values!(ctx, || -> Result<()> { + let path = utils::get_option("path", options, args)?; + if path != "" { + req.set_path(path); + } - let file_size_str = utils::get_option("file_size", options, args); + let file_size_str = utils::get_option("file_size", options, args)?; - if file_size_str != "" { - let file_size = file_size_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid file_size"))?; + if file_size_str != "" { + let file_size = file_size_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid file_size"))?; - req.set_file_size(file_size); - } + req.set_file_size(file_size); + } - let file_mode_str = utils::get_option("file_mode", options, args); + let file_mode_str = utils::get_option("file_mode", options, args)?; - if file_mode_str != "" { - let file_mode = file_mode_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid file_mode"))?; + if file_mode_str != "" { + let file_mode = file_mode_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid file_mode"))?; - req.set_file_mode(file_mode); - } + req.set_file_mode(file_mode); + } - let dir_mode_str = utils::get_option("dir_mode", options, args); + let dir_mode_str = utils::get_option("dir_mode", options, args)?; - if dir_mode_str != "" { - let dir_mode = dir_mode_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid dir_mode"))?; + if dir_mode_str != "" { + let dir_mode = dir_mode_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid dir_mode"))?; - req.set_dir_mode(dir_mode); - } + req.set_dir_mode(dir_mode); + } - let uid_str = utils::get_option("uid", options, args); + let uid_str = utils::get_option("uid", options, args)?; - if uid_str != "" { - let uid = uid_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid uid"))?; + if uid_str != "" { + let uid = uid_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid uid"))?; - req.set_uid(uid); - } + req.set_uid(uid); + } - let gid_str = utils::get_option("gid", options, args); + let gid_str = utils::get_option("gid", options, args)?; - if gid_str != "" { - let gid = gid_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid gid"))?; - req.set_gid(gid); - } + if gid_str != "" { + let gid = gid_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid gid"))?; + req.set_gid(gid); + } - let offset_str = utils::get_option("offset", options, args); + let offset_str = utils::get_option("offset", options, args)?; - if offset_str != "" { - let offset = offset_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid offset"))?; - req.set_offset(offset); - } + if offset_str != "" { + let offset = offset_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid offset"))?; + req.set_offset(offset); + } - let data_str = utils::get_option("data", options, args); - if data_str != "" { - let data = utils::str_to_bytes(&data_str)?; - req.set_data(data.to_vec()); - } + let data_str = utils::get_option("data", options, args)?; + if data_str != "" { + let data = utils::str_to_bytes(&data_str)?; + req.set_data(data.to_vec()); + } + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1658,14 +1747,18 @@ fn agent_cmd_sandbox_reseed_random_dev( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = ReseedRandomDevRequest::default(); + let mut req: ReseedRandomDevRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let str_data = utils::get_option("data", options, args); - let data = utils::str_to_bytes(&str_data)?; + run_if_auto_values!(ctx, || -> Result<()> { + let str_data = utils::get_option("data", options, args)?; + let data = utils::str_to_bytes(&str_data)?; - req.set_data(data.to_vec()); + req.set_data(data.to_vec()); + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1686,39 +1779,43 @@ fn agent_cmd_sandbox_online_cpu_mem( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = OnlineCPUMemRequest::default(); + let mut req: OnlineCPUMemRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let wait_str = utils::get_option("wait", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let wait_str = utils::get_option("wait", options, args)?; - if wait_str != "" { - let wait = wait_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid wait bool"))?; + if wait_str != "" { + let wait = wait_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid wait bool"))?; - req.set_wait(wait); - } + req.set_wait(wait); + } - let nb_cpus_str = utils::get_option("nb_cpus", options, args); + let nb_cpus_str = utils::get_option("nb_cpus", options, args)?; - if nb_cpus_str != "" { - let nb_cpus = nb_cpus_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid nb_cpus value"))?; + if nb_cpus_str != "" { + let nb_cpus = nb_cpus_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid nb_cpus value"))?; - req.set_nb_cpus(nb_cpus); - } + req.set_nb_cpus(nb_cpus); + } - let cpu_only_str = utils::get_option("cpu_only", options, args); + let cpu_only_str = utils::get_option("cpu_only", options, args)?; - if cpu_only_str != "" { - let cpu_only = cpu_only_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid cpu_only bool"))?; + if cpu_only_str != "" { + let cpu_only = cpu_only_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid cpu_only bool"))?; - req.set_cpu_only(cpu_only); - } + req.set_cpu_only(cpu_only); + } + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1739,29 +1836,33 @@ fn agent_cmd_sandbox_set_guest_date_time( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = SetGuestDateTimeRequest::default(); + let mut req: SetGuestDateTimeRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let secs_str = utils::get_option("sec", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let secs_str = utils::get_option("sec", options, args)?; - if secs_str != "" { - let secs = secs_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid seconds"))?; + if secs_str != "" { + let secs = secs_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid seconds"))?; - req.set_Sec(secs); - } + req.set_Sec(secs); + } - let usecs_str = utils::get_option("usec", options, args); + let usecs_str = utils::get_option("usec", options, args)?; - if usecs_str != "" { - let usecs = usecs_str - .parse::() - .map_err(|e| anyhow!(e).context("invalid useconds"))?; + if usecs_str != "" { + let usecs = usecs_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid useconds"))?; - req.set_Usec(usecs); - } + req.set_Usec(usecs); + } + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); @@ -1780,9 +1881,9 @@ fn agent_cmd_sandbox_add_arp_neighbors( client: &AgentServiceClient, _health: &HealthClient, _options: &mut Options, - _args: &str, + args: &str, ) -> Result<()> { - let req = AddARPNeighborsRequest::default(); + let req: AddARPNeighborsRequest = utils::make_request(args)?; let ctx = clone_context(ctx); @@ -1808,13 +1909,17 @@ fn agent_cmd_sandbox_update_container( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = UpdateContainerRequest::default(); + let mut req: UpdateContainerRequest = utils::make_request(args)?; let ctx = clone_context(ctx); - let cid = utils::get_option("cid", options, args); + run_if_auto_values!(ctx, || -> Result<()> { + let cid = utils::get_option("cid", options, args)?; - req.set_container_id(cid); + req.set_container_id(cid); + + Ok(()) + }); // FIXME: Implement fully eprintln!("FIXME: 'UpdateContainer' not fully implemented"); @@ -1838,32 +1943,36 @@ fn agent_cmd_sandbox_mem_hotplug_by_probe( options: &mut Options, args: &str, ) -> Result<()> { - let mut req = MemHotplugByProbeRequest::default(); + let mut req: MemHotplugByProbeRequest = utils::make_request(args)?; let ctx = clone_context(ctx); // Expected to be a comma separated list of hex addresses - let addr_list = utils::get_option("memHotplugProbeAddr", options, args); + let addr_list = utils::get_option("memHotplugProbeAddr", options, args)?; - if addr_list != "" { - let addrs: Vec = addr_list - // Convert into a list of string values. - .split(",") - // Convert each string element into a u8 array of bytes, ignoring - // those elements that fail the conversion. - .filter_map(|s| hex::decode(s.trim_start_matches("0x")).ok()) - // "Stretch" the u8 byte slice into one of length 8 - // (to allow each 8 byte chunk to be converted into a u64). - .map(|mut v| -> Vec { - v.resize(8, 0x0); - v - }) - // Convert the slice of u8 bytes into a u64 - .map(|b| byteorder::LittleEndian::read_u64(&b)) - .collect(); + run_if_auto_values!(ctx, || -> Result<()> { + if !addr_list.is_empty() { + let addrs: Vec = addr_list + // Convert into a list of string values. + .split(",") + // Convert each string element into a u8 array of bytes, ignoring + // those elements that fail the conversion. + .filter_map(|s| hex::decode(s.trim_start_matches("0x")).ok()) + // "Stretch" the u8 byte slice into one of length 8 + // (to allow each 8 byte chunk to be converted into a u64). + .map(|mut v| -> Vec { + v.resize(8, 0x0); + v + }) + // Convert the slice of u8 bytes into a u64 + .map(|b| byteorder::LittleEndian::read_u64(&b)) + .collect(); - req.set_memHotplugProbeAddr(addrs); - } + req.set_memHotplugProbeAddr(addrs); + } + + Ok(()) + }); debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); diff --git a/tools/agent-ctl/src/main.rs b/tools/agent-ctl/src/main.rs index ab4a90e45c..00519c9325 100644 --- a/tools/agent-ctl/src/main.rs +++ b/tools/agent-ctl/src/main.rs @@ -96,6 +96,15 @@ fn make_examples_text(program_name: &str) -> String { $ {program} connect --server-address "{vsock_server_address}" --repeat -1 --cmd GetGuestDetails +- Query guest details, asking for full details by specifying the API request object in JSON format: + + $ {program} connect --server-address "{vsock_server_address}" -c 'GetGuestDetails json://{{"mem_block_size": true, "mem_hotplug_probe": true}}' + +- Query guest details, asking for extra detail by partially specifying the API request object in JSON format from a file: + + $ echo '{{"mem_block_size": true}}' > /tmp/api.json + $ {program} connect --server-address "{vsock_server_address}" -c 'GetGuestDetails file:///tmp/api.json' + - Send a 'SIGUSR1' signal to a container process: $ {program} connect --server-address "{vsock_server_address}" --cmd 'SignalProcess signal=usr1 sid={sandbox_id} cid={container_id}' @@ -169,6 +178,7 @@ fn connect(name: &str, global_args: clap::ArgMatches) -> Result<()> { let bundle_dir = args.value_of("bundle-dir").unwrap_or("").to_string(); let hybrid_vsock = args.is_present("hybrid-vsock"); + let no_auto_values = args.is_present("no-auto-values"); let cfg = Config { server_address, @@ -178,6 +188,7 @@ fn connect(name: &str, global_args: clap::ArgMatches) -> Result<()> { timeout_nano, hybrid_vsock_port, hybrid_vsock, + no_auto_values, }; let result = rpc::run(&logger, &cfg, commands); @@ -255,6 +266,12 @@ fn real_main() -> Result<()> { .long("interactive") .help("Allow interactive client"), ) + .arg( + Arg::with_name("no-auto-values") + .short("n") + .long("no-auto-values") + .help("Disable automatic generation of values for sandbox ID, container ID, etc"), + ) .arg( Arg::with_name("server-address") .long("server-address") diff --git a/tools/agent-ctl/src/types.rs b/tools/agent-ctl/src/types.rs index b5d8f31a5c..300ebe1704 100644 --- a/tools/agent-ctl/src/types.rs +++ b/tools/agent-ctl/src/types.rs @@ -18,4 +18,5 @@ pub struct Config { pub interactive: bool, pub hybrid_vsock: bool, pub ignore_errors: bool, + pub no_auto_values: bool, } diff --git a/tools/agent-ctl/src/utils.rs b/tools/agent-ctl/src/utils.rs index 15761cdea2..41235494c8 100644 --- a/tools/agent-ctl/src/utils.rs +++ b/tools/agent-ctl/src/utils.rs @@ -23,8 +23,10 @@ use protocols::oci::{ User as ttrpcUser, }; use rand::Rng; +use serde::de::DeserializeOwned; use slog::{debug, warn}; use std::collections::HashMap; +use std::fs::File; use std::path::PathBuf; use std::sync::{Arc, Mutex}; @@ -86,7 +88,7 @@ lazy_static! { } pub fn signame_to_signum(name: &str) -> Result { - if name == "" { + if name.is_empty() { return Err(anyhow!("invalid signal")); } @@ -121,7 +123,7 @@ pub fn signame_to_signum(name: &str) -> Result { // Convert a human time fornat (like "2s") into the equivalent number // of nano seconds. pub fn human_time_to_ns(human_time: &str) -> Result { - if human_time == "" || human_time == "0" { + if human_time.is_empty() || human_time.eq("0") { return Ok(0); } @@ -143,17 +145,17 @@ pub fn human_time_to_ns(human_time: &str) -> Result { // - All other options values default to an empty string. // - All options are saved in the global hash before being returned for future // use. -pub fn get_option(name: &str, options: &mut Options, args: &str) -> String { +pub fn get_option(name: &str, options: &mut Options, args: &str) -> Result { let words: Vec<&str> = args.split_whitespace().collect(); for word in words { - let fields: Vec = word.split("=").map(|s| s.to_string()).collect(); + let fields: Vec = word.split('=').map(|s| s.to_string()).collect(); if fields.len() < 2 { continue; } - if fields[0] == "" { + if fields[0].is_empty() { continue; } @@ -162,17 +164,10 @@ pub fn get_option(name: &str, options: &mut Options, args: &str) -> String { let mut value = fields[1..].join("="); // Expand "spec=file:///some/where/config.json" - if key == "spec" && value.starts_with(FILE_URI) { - let spec_file = match uri_to_filename(&value) { - Ok(file) => file, - Err(e) => { - warn!(sl!(), "failed to handle spec file URI: {:}", e); + if key.eq("spec") && value.starts_with(FILE_URI) { + let (_, spec_file) = split_uri(&value)?; - "".to_string() - } - }; - - if spec_file != "" { + if !spec_file.is_empty() { value = match spec_file_to_string(spec_file) { Ok(s) => s, Err(e) => { @@ -197,7 +192,7 @@ pub fn get_option(name: &str, options: &mut Options, args: &str) -> String { if let Some(value) = options.get(name) { debug!(sl!(), "using option {:?}={:?} ({})", name, value, msg); - return value.to_string(); + return Ok(value.into()); } msg = "generated"; @@ -210,14 +205,14 @@ pub fn get_option(name: &str, options: &mut Options, args: &str) -> String { // Default to CID "exec_id" => { msg = "derived"; - //derived = true; match options.get("cid") { - Some(value) => value.to_string(), - None => "".to_string(), + Some(value) => value, + None => "", } + .into() } - _ => "".to_string(), + _ => "".into(), }; debug!(sl!(), "using option {:?}={:?} ({})", name, value, msg); @@ -225,7 +220,7 @@ pub fn get_option(name: &str, options: &mut Options, args: &str) -> String { // Store auto-generated value options.insert(name.to_string(), value.to_string()); - value + Ok(value) } pub fn generate_random_hex_string(len: u32) -> String { @@ -252,7 +247,7 @@ pub fn random_container_id() -> String { } fn config_file_from_bundle_dir(bundle_dir: &str) -> Result { - if bundle_dir == "" { + if bundle_dir.is_empty() { return Err(anyhow!("missing bundle directory")); } @@ -739,18 +734,20 @@ fn oci_to_ttrpc(bundle_dir: &str, cid: &str, oci: &ociSpec) -> Result Ok(ttrpc_spec) } -fn uri_to_filename(uri: &str) -> Result { - if !uri.starts_with(FILE_URI) { - return Err(anyhow!(format!("invalid URI: {:?}", uri))); - } +// Split a URI and return a tuple comprising the scheme and the data. +// +// Note that we have to use our own parsing since "json://" is not +// an official schema ;( +fn split_uri(uri: &str) -> Result<(String, String)> { + const URI_DELIMITER: &str = "://"; - let fields: Vec<&str> = uri.split(FILE_URI).collect(); + let fields: Vec<&str> = uri.split(URI_DELIMITER).collect(); if fields.len() != 2 { - return Err(anyhow!(format!("invalid URI: {:?}", uri))); + return Err(anyhow!("invalid URI: {:?}", uri)); } - Ok(fields[1].to_string()) + Ok((fields[0].into(), fields[1].into())) } pub fn spec_file_to_string(spec_file: String) -> Result { @@ -766,9 +763,9 @@ pub fn get_oci_spec_json(cfg: &Config) -> Result { } pub fn get_ttrpc_spec(options: &mut Options, cid: &str) -> Result { - let bundle_dir = get_option("bundle-dir", options, ""); + let bundle_dir = get_option("bundle-dir", options, "")?; - let json_spec = get_option("spec", options, ""); + let json_spec = get_option("spec", options, "")?; assert_ne!(json_spec, ""); let oci_spec: ociSpec = serde_json::from_str(&json_spec).map_err(|e| anyhow!(e))?; @@ -789,3 +786,67 @@ pub fn str_to_bytes(s: &str) -> Result> { Ok(s.as_bytes().to_vec()) } } + +// Returns a request object of the type requested. +// +// Call as: +// +// ```rust +// let req1: SomeType = make_request(args)?; +// let req2: AnotherType = make_request(args)?; +// ``` +// +// The args string can take a number of forms: +// +// - A file URI: +// +// The string is expected to start with 'file://' with the full path to +// a local file containing a complete or partial JSON document. +// +// Example: 'file:///some/where/foo.json' +// +// - A JSON URI: +// +// This invented 'json://{ ...}' URI allows either a complete JSON document +// or a partial JSON fragment to be specified. The JSON takes the form of +// the JSON serialised protocol buffers files that specify the Kata Agent +// API. +// +// - If the complete document for the specified type is provided, the values +// specified are deserialised into the returned +// type. +// +// - If a partial document is provided, the values specified are +// deserialised into the returned type and all remaining elements take their +// default values. +// +// - If no values are specified, all returned type will be created as +// if TypeName::default() had been specified instead. +// +// Example 1 (Complete and valid empty JSON document): 'json://{}' +// Example 2 (Valid partial JSON document): 'json://{"foo": true, "bar": "hello"}' +// Example 3 (GetGuestDetails API example): +// +// let args = r#"json://{"mem_block_size": true, "mem_hotplug_probe": true}"#; +// +// let req: GetGuestDetailsRequest = make_request(args)?; +// +pub fn make_request(args: &str) -> Result { + if args.is_empty() { + return Ok(Default::default()); + } + + let (scheme, data) = split_uri(args)?; + + match scheme.as_str() { + "json" => Ok(serde_json::from_str(&data)?), + "file" => { + let file = File::open(data)?; + + Ok(serde_json::from_reader(file)?) + } + // Don't error since the args may contain key=value pairs which + // are not handled by this functionz. + _ => Ok(Default::default()), + } +}