agent-ctl: Allow API specification in JSON format

Update the `agent-ctl` tool to allow API fields to be specified in JSON
format, either directly on the command-line, or via a file URI.

This feature is made possible by enabling `serde` support in the agent
`protocols` crate. Careful use of the `serde` macros allows the
`agent-ctl` tool to accept _partially_ specified API objects in JSON
format; fields that are not specified are set to the default value for
their respective types.

`build.rs` changes based on work by Fupan.

Fixes: #2978.

Contributions-by: Fupan Li <lifupan@gmail.com>
Contributions-by: Bin Liu <bin@hyper.sh>
Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
This commit is contained in:
James O. D. Hunt 2021-10-26 14:02:07 +01:00
parent 18c47fe8f3
commit 8ab90e1068
10 changed files with 729 additions and 351 deletions

49
src/agent/Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 3
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.15.1" version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03345e98af8f3d786b6d9f656ccfa6ac316d954e92bc4841f0bba20789d5fb5a" checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd"
dependencies = [ dependencies = [
"gimli", "gimli",
] ]
@ -83,9 +83,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.59" version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cc", "cc",
@ -414,15 +414,15 @@ dependencies = [
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.24.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
@ -871,9 +871,12 @@ dependencies = [
[[package]] [[package]]
name = "object" name = "object"
version = "0.24.0" version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "oci" name = "oci"
@ -1138,6 +1141,10 @@ name = "protobuf"
version = "2.14.0" version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e86d370532557ae7573551a1ec8235a0f8d6cb276c7c9e6aa490b511c447485" checksum = "8e86d370532557ae7573551a1ec8235a0f8d6cb276c7c9e6aa490b511c447485"
dependencies = [
"serde",
"serde_derive",
]
[[package]] [[package]]
name = "protobuf-codegen" name = "protobuf-codegen"
@ -1164,6 +1171,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"protobuf", "protobuf",
"serde",
"serde_json",
"ttrpc", "ttrpc",
"ttrpc-codegen", "ttrpc-codegen",
] ]
@ -1304,9 +1313,9 @@ dependencies = [
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.19" version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]] [[package]]
name = "rustjail" name = "rustjail"
@ -1364,18 +1373,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.129" version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.129" version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [ dependencies = [
"proc-macro2 1.0.26", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
@ -1384,9 +1393,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.64" version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -1804,9 +1813,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.7.1" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"

View File

@ -4,10 +4,16 @@ version = "0.1.0"
authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>"] authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>"]
edition = "2018" edition = "2018"
[features]
default = []
with-serde = [ "serde", "serde_json" ]
[dependencies] [dependencies]
ttrpc = { version = "0.5.0", features = ["async"] } ttrpc = { version = "0.5.0", features = ["async"] }
async-trait = "0.1.42" 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] [build-dependencies]
ttrpc-codegen = "0.2.0" ttrpc-codegen = "0.2.0"

View File

@ -3,29 +3,148 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// //
use std::fs; use std::fs::File;
use ttrpc_codegen::{Codegen, Customize}; 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![ let protos = vec![
"protos/types.proto",
"protos/agent.proto", "protos/agent.proto",
"protos/health.proto",
"protos/google/protobuf/empty.proto", "protos/google/protobuf/empty.proto",
"protos/health.proto",
"protos/oci.proto", "protos/oci.proto",
"protos/types.proto",
]; ];
Codegen::new() // Tell Cargo that if the .proto files changed, to rerun this build script.
.out_dir("src") protos
.inputs(&protos) .iter()
.include("protos") .for_each(|p| println!("cargo:rerun-if-changed={}", &p));
.rust_protobuf()
.customize(Customize { let ttrpc_options = Customize {
async_server: true, async_server: true,
..Default::default() ..Default::default()
}) };
.run()
.expect("Gen codes failed."); let protobuf_options = ProtobufCustomize {
serde_derive: Some(true),
..Default::default()
};
let out_dir = Path::new("src");
Codegen::new()
.out_dir(out_dir)
.inputs(&protos)
.include("protos")
.customize(ttrpc_options)
.rust_protobuf()
.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 // There is a message named 'Box' in oci.proto
// so there is a struct named 'Box', we should replace Box<Self> to ::std::boxed::Box<Self> // so there is a struct named 'Box', we should replace Box<Self> to ::std::boxed::Box<Self>
@ -34,11 +153,16 @@ fn main() {
"src/oci.rs", "src/oci.rs",
"self: Box<Self>", "self: Box<Self>",
"self: ::std::boxed::Box<Self>", "self: ::std::boxed::Box<Self>",
) )?;
.unwrap();
use_serde(&protos, out_dir)?;
Ok(())
} }
fn replace_text_in_file(file_name: &str, from: &str, to: &str) -> Result<(), std::io::Error> { fn main() {
let new_contents = fs::read_to_string(file_name)?.replace(from, to); if let Err(e) = real_main() {
fs::write(&file_name, new_contents.as_bytes()) eprintln!("ERROR: {}", e);
exit(1);
}
} }

View File

@ -818,6 +818,10 @@ name = "protobuf"
version = "2.14.0" version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e86d370532557ae7573551a1ec8235a0f8d6cb276c7c9e6aa490b511c447485" checksum = "8e86d370532557ae7573551a1ec8235a0f8d6cb276c7c9e6aa490b511c447485"
dependencies = [
"serde",
"serde_derive",
]
[[package]] [[package]]
name = "protobuf-codegen" name = "protobuf-codegen"
@ -844,6 +848,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"protobuf", "protobuf",
"serde",
"serde_json",
"ttrpc", "ttrpc",
"ttrpc-codegen", "ttrpc-codegen",
] ]
@ -1050,18 +1056,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.126" version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.126" version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [ dependencies = [
"proc-macro2 1.0.26", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
@ -1070,9 +1076,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.64" version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",

View File

@ -10,7 +10,7 @@ authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
protocols = { path = "../../src/agent/protocols" } protocols = { path = "../../src/agent/protocols", features = ["with-serde"] }
rustjail = { path = "../../src/agent/rustjail" } rustjail = { path = "../../src/agent/rustjail" }
oci = { path = "../../src/agent/oci" } oci = { path = "../../src/agent/oci" }
@ -20,6 +20,8 @@ anyhow = "1.0.31"
hex = "0.4.2" hex = "0.4.2"
byteorder = "1.3.4" 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" } logging = { path = "../../pkg/logging" }
slog = "2.5.2" slog = "2.5.2"
slog-scope = "4.3.0" slog-scope = "4.3.0"
@ -35,5 +37,5 @@ ttrpc = { version = "0.5.0" }
humantime = "2.0.0" humantime = "2.0.0"
# For Options (state passing) # For Options (state passing)
serde = { version = "1.0.110", features = ["derive"] } serde = { version = "1.0.130", features = ["derive"] }
serde_json = "1.0.53" serde_json = "1.0.68"

View File

@ -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 - $ 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 ### Connect to a real Kata Container
The method used to connect to Kata Containers agent depends on the configured The method used to connect to Kata Containers agent depends on the configured

View File

@ -25,6 +25,23 @@ use std::time::Duration;
use ttrpc; use ttrpc;
use ttrpc::context::Context; 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. // Hack until the actual Context type supports this.
fn clone_context(ctx: &Context) -> Context { fn clone_context(ctx: &Context) -> Context {
Context { Context {
@ -82,6 +99,13 @@ const DEFAULT_PROC_SIGNAL: &'static str = "SIGKILL";
const ERR_API_FAILED: &str = "API failed"; 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] = &[ static AGENT_CMDS: &'static [AgentCmd] = &[
AgentCmd { AgentCmd {
name: "AddARPNeighbors", name: "AddARPNeighbors",
@ -311,7 +335,7 @@ fn get_agent_cmd_details() -> Vec<String> {
fn get_agent_cmd_func(name: &str) -> Result<AgentCmdFp> { fn get_agent_cmd_func(name: &str) -> Result<AgentCmdFp> {
for cmd in AGENT_CMDS { for cmd in AGENT_CMDS {
if cmd.name == name { if cmd.name.eq(name) {
return Ok(cmd.fp); return Ok(cmd.fp);
} }
} }
@ -339,7 +363,7 @@ fn get_all_cmd_details() -> Vec<String> {
fn get_builtin_cmd_func(name: &str) -> Result<BuiltinCmdFp> { fn get_builtin_cmd_func(name: &str) -> Result<BuiltinCmdFp> {
for cmd in BUILTIN_CMDS { for cmd in BUILTIN_CMDS {
if cmd.name == name { if cmd.name.eq(name) {
return Ok(cmd.fp); return Ok(cmd.fp);
} }
} }
@ -416,7 +440,7 @@ fn create_ttrpc_client(
hybrid_vsock_port: u64, hybrid_vsock_port: u64,
hybrid_vsock: bool, hybrid_vsock: bool,
) -> Result<ttrpc::Client> { ) -> Result<ttrpc::Client> {
if server_address == "" { if server_address.is_empty() {
return Err(anyhow!("server address cannot be blank")); 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<()> { 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"); println!("Built-in commands:\n");
let mut builtin_cmds = get_builtin_cmd_details(); 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 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 // Special-case loading the OCI config file so it is accessible
// to all commands. // to all commands.
@ -654,7 +687,7 @@ fn handle_cmd(
let cmd = fields[0]; let cmd = fields[0];
if cmd == "" { if cmd.is_empty() {
// Ignore empty commands // Ignore empty commands
return (Ok(()), false); return (Ok(()), false);
} }
@ -754,7 +787,7 @@ fn handle_agent_cmd(
return (result, false); return (result, false);
} }
let shutdown = cmd == SHUTDOWN_CMD; let shutdown = cmd.eq(SHUTDOWN_CMD);
(Ok(()), shutdown) (Ok(()), shutdown)
} }
@ -777,7 +810,7 @@ fn interactive_client_loop(
let cmdline = let cmdline =
readline("Enter command").map_err(|e| anyhow!(e).context("failed to read line"))?; readline("Enter command").map_err(|e| anyhow!(e).context("failed to read line"))?;
if cmdline == "" { if cmdline.is_empty() {
continue; continue;
} }
@ -825,15 +858,12 @@ fn agent_cmd_health_check(
_client: &AgentServiceClient, _client: &AgentServiceClient,
health: &HealthClient, health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = CheckRequest::default(); let req: CheckRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
// value unused
req.set_service("".to_string());
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = health let reply = health
@ -851,16 +881,13 @@ fn agent_cmd_health_version(
_client: &AgentServiceClient, _client: &AgentServiceClient,
health: &HealthClient, health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
// XXX: Yes, the API is actually broken! // XXX: Yes, the API is actually broken!
let mut req = CheckRequest::default(); let req: CheckRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
// value unused
req.set_service("".to_string());
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = health let reply = health
@ -880,13 +907,17 @@ fn agent_cmd_sandbox_create(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = CreateSandboxRequest::default(); let mut req: CreateSandboxRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let sid = utils::get_option("sid", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let sid = utils::get_option("sid", options, args)?;
req.set_sandbox_id(sid); req.set_sandbox_id(sid);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -904,9 +935,9 @@ fn agent_cmd_sandbox_destroy(
client: &AgentServiceClient, client: &AgentServiceClient,
_health: &HealthClient, _health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let req = DestroySandboxRequest::default(); let req: DestroySandboxRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
@ -929,21 +960,24 @@ fn agent_cmd_container_create(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = CreateContainerRequest::default(); let mut req: CreateContainerRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); 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 // FIXME: container create: add back "spec=file:///" support
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))?;
req.set_container_id(cid); req.set_container_id(cid);
req.set_exec_id(exec_id); req.set_exec_id(exec_id);
req.set_OCI(ttrpc_spec); req.set_OCI(ttrpc_spec);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -963,13 +997,15 @@ fn agent_cmd_container_remove(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = RemoveContainerRequest::default(); let mut req: RemoveContainerRequest = utils::make_request(args)?;
let cid = utils::get_option("cid", options, args);
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
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)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
@ -990,12 +1026,13 @@ fn agent_cmd_container_exec(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = ExecProcessRequest::default(); let mut req: ExecProcessRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let exec_id = utils::get_option("exec_id", options, args); 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))?;
@ -1017,6 +1054,9 @@ fn agent_cmd_container_exec(
req.set_exec_id(exec_id); req.set_exec_id(exec_id);
req.set_process(process); req.set_process(process);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1036,13 +1076,16 @@ fn agent_cmd_container_stats(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = StatsContainerRequest::default(); let mut req: StatsContainerRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); 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)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
@ -1063,13 +1106,16 @@ fn agent_cmd_container_pause(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = PauseContainerRequest::default(); let mut req: PauseContainerRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); 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)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
@ -1090,13 +1136,16 @@ fn agent_cmd_container_resume(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = ResumeContainerRequest::default(); let mut req: ResumeContainerRequest = utils::make_request(args)?;
let cid = utils::get_option("cid", options, args);
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
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)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
@ -1117,13 +1166,16 @@ fn agent_cmd_container_start(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = StartContainerRequest::default(); let mut req: StartContainerRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); 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)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
@ -1142,13 +1194,18 @@ fn agent_cmd_sandbox_get_guest_details(
client: &AgentServiceClient, client: &AgentServiceClient,
_health: &HealthClient, _health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = GuestDetailsRequest::default(); let mut req: GuestDetailsRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
run_if_auto_values!(ctx, || -> Result<()> {
req.set_mem_block_size(true); req.set_mem_block_size(true);
req.set_mem_hotplug_probe(true);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
@ -1169,16 +1226,20 @@ fn agent_cmd_container_wait_process(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = WaitProcessRequest::default(); let mut req: WaitProcessRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let exec_id = utils::get_option("exec_id", options, args); 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_container_id(cid);
req.set_exec_id(exec_id); req.set_exec_id(exec_id);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1198,17 +1259,18 @@ fn agent_cmd_container_signal_process(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = SignalProcessRequest::default(); let mut req: SignalProcessRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let exec_id = utils::get_option("exec_id", options, args); 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 // Convert to a numeric
if sigstr == "" { if sigstr.is_empty() {
sigstr = DEFAULT_PROC_SIGNAL.to_string(); sigstr = DEFAULT_PROC_SIGNAL.to_string();
} }
@ -1218,6 +1280,9 @@ fn agent_cmd_container_signal_process(
req.set_exec_id(exec_id); req.set_exec_id(exec_id);
req.set_signal(signum as u32); req.set_signal(signum as u32);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1235,9 +1300,9 @@ fn agent_cmd_sandbox_update_interface(
client: &AgentServiceClient, client: &AgentServiceClient,
_health: &HealthClient, _health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let req = UpdateInterfaceRequest::default(); let req: UpdateInterfaceRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
@ -1260,9 +1325,9 @@ fn agent_cmd_sandbox_update_routes(
client: &AgentServiceClient, client: &AgentServiceClient,
_health: &HealthClient, _health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let req = UpdateRoutesRequest::default(); let req: UpdateRoutesRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
@ -1286,9 +1351,9 @@ fn agent_cmd_sandbox_list_interfaces(
client: &AgentServiceClient, client: &AgentServiceClient,
_health: &HealthClient, _health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let req = ListInterfacesRequest::default(); let req: ListInterfacesRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
@ -1309,9 +1374,9 @@ fn agent_cmd_sandbox_list_routes(
client: &AgentServiceClient, client: &AgentServiceClient,
_health: &HealthClient, _health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let req = ListRoutesRequest::default(); let req: ListRoutesRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
@ -1334,17 +1399,18 @@ fn agent_cmd_container_tty_win_resize(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = TtyWinResizeRequest::default(); let mut req: TtyWinResizeRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let exec_id = utils::get_option("exec_id", options, args); 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_container_id(cid);
req.set_exec_id(exec_id); 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 != "" { if rows_str != "" {
let rows = rows_str let rows = rows_str
@ -1353,7 +1419,7 @@ fn agent_cmd_container_tty_win_resize(
req.set_row(rows); 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 != "" { if cols_str != "" {
let cols = cols_str let cols = cols_str
@ -1363,6 +1429,9 @@ fn agent_cmd_container_tty_win_resize(
req.set_column(cols); req.set_column(cols);
} }
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1382,16 +1451,20 @@ fn agent_cmd_container_close_stdin(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = CloseStdinRequest::default(); let mut req: CloseStdinRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let exec_id = utils::get_option("exec_id", options, args); 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_container_id(cid);
req.set_exec_id(exec_id); req.set_exec_id(exec_id);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1411,17 +1484,18 @@ fn agent_cmd_container_read_stdout(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = ReadStreamRequest::default(); let mut req: ReadStreamRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let exec_id = utils::get_option("exec_id", options, args); 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_container_id(cid);
req.set_exec_id(exec_id); 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 != "" { if length_str != "" {
let length = length_str let length = length_str
@ -1430,6 +1504,9 @@ fn agent_cmd_container_read_stdout(
req.set_len(length); req.set_len(length);
} }
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1449,17 +1526,18 @@ fn agent_cmd_container_read_stderr(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = ReadStreamRequest::default(); let mut req: ReadStreamRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let exec_id = utils::get_option("exec_id", options, args); 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_container_id(cid);
req.set_exec_id(exec_id); 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 != "" { if length_str != "" {
let length = length_str let length = length_str
@ -1468,6 +1546,9 @@ fn agent_cmd_container_read_stderr(
req.set_len(length); req.set_len(length);
} }
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1487,20 +1568,24 @@ fn agent_cmd_container_write_stdin(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = WriteStreamRequest::default(); let mut req: WriteStreamRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let exec_id = utils::get_option("exec_id", options, args); 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 str_data = utils::get_option("data", options, args)?;
let data = utils::str_to_bytes(&str_data)?; let data = utils::str_to_bytes(&str_data)?;
req.set_container_id(cid); req.set_container_id(cid);
req.set_exec_id(exec_id); req.set_exec_id(exec_id);
req.set_data(data.to_vec()); req.set_data(data.to_vec());
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1518,9 +1603,9 @@ fn agent_cmd_sandbox_get_metrics(
client: &AgentServiceClient, client: &AgentServiceClient,
_health: &HealthClient, _health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let req = GetMetricsRequest::default(); let req: GetMetricsRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
@ -1541,9 +1626,9 @@ fn agent_cmd_sandbox_get_oom_event(
client: &AgentServiceClient, client: &AgentServiceClient,
_health: &HealthClient, _health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let req = GetOOMEventRequest::default(); let req: GetOOMEventRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
@ -1566,16 +1651,17 @@ fn agent_cmd_sandbox_copy_file(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = CopyFileRequest::default(); let mut req: CopyFileRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let path = utils::get_option("path", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let path = utils::get_option("path", options, args)?;
if path != "" { if path != "" {
req.set_path(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 != "" { if file_size_str != "" {
let file_size = file_size_str let file_size = file_size_str
@ -1585,7 +1671,7 @@ fn agent_cmd_sandbox_copy_file(
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 != "" { if file_mode_str != "" {
let file_mode = file_mode_str let file_mode = file_mode_str
@ -1595,7 +1681,7 @@ fn agent_cmd_sandbox_copy_file(
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 != "" { if dir_mode_str != "" {
let dir_mode = dir_mode_str let dir_mode = dir_mode_str
@ -1605,7 +1691,7 @@ fn agent_cmd_sandbox_copy_file(
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 != "" { if uid_str != "" {
let uid = uid_str let uid = uid_str
@ -1615,7 +1701,7 @@ fn agent_cmd_sandbox_copy_file(
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 != "" { if gid_str != "" {
let gid = gid_str let gid = gid_str
@ -1624,7 +1710,7 @@ fn agent_cmd_sandbox_copy_file(
req.set_gid(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 != "" { if offset_str != "" {
let offset = offset_str let offset = offset_str
@ -1633,12 +1719,15 @@ fn agent_cmd_sandbox_copy_file(
req.set_offset(offset); req.set_offset(offset);
} }
let data_str = utils::get_option("data", options, args); let data_str = utils::get_option("data", options, args)?;
if data_str != "" { if data_str != "" {
let data = utils::str_to_bytes(&data_str)?; let data = utils::str_to_bytes(&data_str)?;
req.set_data(data.to_vec()); req.set_data(data.to_vec());
} }
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1658,15 +1747,19 @@ fn agent_cmd_sandbox_reseed_random_dev(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = ReseedRandomDevRequest::default(); let mut req: ReseedRandomDevRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
let str_data = utils::get_option("data", options, args); run_if_auto_values!(ctx, || -> Result<()> {
let str_data = utils::get_option("data", options, args)?;
let data = utils::str_to_bytes(&str_data)?; 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)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1686,11 +1779,12 @@ fn agent_cmd_sandbox_online_cpu_mem(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = OnlineCPUMemRequest::default(); let mut req: OnlineCPUMemRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); 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 != "" { if wait_str != "" {
let wait = wait_str let wait = wait_str
@ -1700,7 +1794,7 @@ fn agent_cmd_sandbox_online_cpu_mem(
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 != "" { if nb_cpus_str != "" {
let nb_cpus = nb_cpus_str let nb_cpus = nb_cpus_str
@ -1710,7 +1804,7 @@ fn agent_cmd_sandbox_online_cpu_mem(
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 != "" { if cpu_only_str != "" {
let cpu_only = cpu_only_str let cpu_only = cpu_only_str
@ -1720,6 +1814,9 @@ fn agent_cmd_sandbox_online_cpu_mem(
req.set_cpu_only(cpu_only); req.set_cpu_only(cpu_only);
} }
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1739,11 +1836,12 @@ fn agent_cmd_sandbox_set_guest_date_time(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = SetGuestDateTimeRequest::default(); let mut req: SetGuestDateTimeRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); 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 != "" { if secs_str != "" {
let secs = secs_str let secs = secs_str
@ -1753,7 +1851,7 @@ fn agent_cmd_sandbox_set_guest_date_time(
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 != "" { if usecs_str != "" {
let usecs = usecs_str let usecs = usecs_str
@ -1763,6 +1861,9 @@ fn agent_cmd_sandbox_set_guest_date_time(
req.set_Usec(usecs); req.set_Usec(usecs);
} }
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client
@ -1780,9 +1881,9 @@ fn agent_cmd_sandbox_add_arp_neighbors(
client: &AgentServiceClient, client: &AgentServiceClient,
_health: &HealthClient, _health: &HealthClient,
_options: &mut Options, _options: &mut Options,
_args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let req = AddARPNeighborsRequest::default(); let req: AddARPNeighborsRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
@ -1808,14 +1909,18 @@ fn agent_cmd_sandbox_update_container(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = UpdateContainerRequest::default(); let mut req: UpdateContainerRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); 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 // FIXME: Implement fully
eprintln!("FIXME: 'UpdateContainer' not fully implemented"); eprintln!("FIXME: 'UpdateContainer' not fully implemented");
@ -1838,14 +1943,15 @@ fn agent_cmd_sandbox_mem_hotplug_by_probe(
options: &mut Options, options: &mut Options,
args: &str, args: &str,
) -> Result<()> { ) -> Result<()> {
let mut req = MemHotplugByProbeRequest::default(); let mut req: MemHotplugByProbeRequest = utils::make_request(args)?;
let ctx = clone_context(ctx); let ctx = clone_context(ctx);
// Expected to be a comma separated list of hex addresses // 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 != "" { run_if_auto_values!(ctx, || -> Result<()> {
if !addr_list.is_empty() {
let addrs: Vec<u64> = addr_list let addrs: Vec<u64> = addr_list
// Convert into a list of string values. // Convert into a list of string values.
.split(",") .split(",")
@ -1865,6 +1971,9 @@ fn agent_cmd_sandbox_mem_hotplug_by_probe(
req.set_memHotplugProbeAddr(addrs); req.set_memHotplugProbeAddr(addrs);
} }
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client let reply = client

View File

@ -96,6 +96,15 @@ fn make_examples_text(program_name: &str) -> String {
$ {program} connect --server-address "{vsock_server_address}" --repeat -1 --cmd GetGuestDetails $ {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: - 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}' $ {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 bundle_dir = args.value_of("bundle-dir").unwrap_or("").to_string();
let hybrid_vsock = args.is_present("hybrid-vsock"); let hybrid_vsock = args.is_present("hybrid-vsock");
let no_auto_values = args.is_present("no-auto-values");
let cfg = Config { let cfg = Config {
server_address, server_address,
@ -178,6 +188,7 @@ fn connect(name: &str, global_args: clap::ArgMatches) -> Result<()> {
timeout_nano, timeout_nano,
hybrid_vsock_port, hybrid_vsock_port,
hybrid_vsock, hybrid_vsock,
no_auto_values,
}; };
let result = rpc::run(&logger, &cfg, commands); let result = rpc::run(&logger, &cfg, commands);
@ -255,6 +266,12 @@ fn real_main() -> Result<()> {
.long("interactive") .long("interactive")
.help("Allow interactive client"), .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(
Arg::with_name("server-address") Arg::with_name("server-address")
.long("server-address") .long("server-address")

View File

@ -18,4 +18,5 @@ pub struct Config {
pub interactive: bool, pub interactive: bool,
pub hybrid_vsock: bool, pub hybrid_vsock: bool,
pub ignore_errors: bool, pub ignore_errors: bool,
pub no_auto_values: bool,
} }

View File

@ -23,8 +23,10 @@ use protocols::oci::{
User as ttrpcUser, User as ttrpcUser,
}; };
use rand::Rng; use rand::Rng;
use serde::de::DeserializeOwned;
use slog::{debug, warn}; use slog::{debug, warn};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -86,7 +88,7 @@ lazy_static! {
} }
pub fn signame_to_signum(name: &str) -> Result<u8> { pub fn signame_to_signum(name: &str) -> Result<u8> {
if name == "" { if name.is_empty() {
return Err(anyhow!("invalid signal")); return Err(anyhow!("invalid signal"));
} }
@ -121,7 +123,7 @@ pub fn signame_to_signum(name: &str) -> Result<u8> {
// Convert a human time fornat (like "2s") into the equivalent number // Convert a human time fornat (like "2s") into the equivalent number
// of nano seconds. // of nano seconds.
pub fn human_time_to_ns(human_time: &str) -> Result<i64> { pub fn human_time_to_ns(human_time: &str) -> Result<i64> {
if human_time == "" || human_time == "0" { if human_time.is_empty() || human_time.eq("0") {
return Ok(0); return Ok(0);
} }
@ -143,17 +145,17 @@ pub fn human_time_to_ns(human_time: &str) -> Result<i64> {
// - All other options values default to an empty string. // - All other options values default to an empty string.
// - All options are saved in the global hash before being returned for future // - All options are saved in the global hash before being returned for future
// use. // 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<String> {
let words: Vec<&str> = args.split_whitespace().collect(); let words: Vec<&str> = args.split_whitespace().collect();
for word in words { for word in words {
let fields: Vec<String> = word.split("=").map(|s| s.to_string()).collect(); let fields: Vec<String> = word.split('=').map(|s| s.to_string()).collect();
if fields.len() < 2 { if fields.len() < 2 {
continue; continue;
} }
if fields[0] == "" { if fields[0].is_empty() {
continue; continue;
} }
@ -162,17 +164,10 @@ pub fn get_option(name: &str, options: &mut Options, args: &str) -> String {
let mut value = fields[1..].join("="); let mut value = fields[1..].join("=");
// Expand "spec=file:///some/where/config.json" // Expand "spec=file:///some/where/config.json"
if key == "spec" && value.starts_with(FILE_URI) { if key.eq("spec") && value.starts_with(FILE_URI) {
let spec_file = match uri_to_filename(&value) { let (_, spec_file) = split_uri(&value)?;
Ok(file) => file,
Err(e) => {
warn!(sl!(), "failed to handle spec file URI: {:}", e);
"".to_string() if !spec_file.is_empty() {
}
};
if spec_file != "" {
value = match spec_file_to_string(spec_file) { value = match spec_file_to_string(spec_file) {
Ok(s) => s, Ok(s) => s,
Err(e) => { 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) { if let Some(value) = options.get(name) {
debug!(sl!(), "using option {:?}={:?} ({})", name, value, msg); debug!(sl!(), "using option {:?}={:?} ({})", name, value, msg);
return value.to_string(); return Ok(value.into());
} }
msg = "generated"; msg = "generated";
@ -210,14 +205,14 @@ pub fn get_option(name: &str, options: &mut Options, args: &str) -> String {
// Default to CID // Default to CID
"exec_id" => { "exec_id" => {
msg = "derived"; msg = "derived";
//derived = true;
match options.get("cid") { match options.get("cid") {
Some(value) => value.to_string(), Some(value) => value,
None => "".to_string(), None => "",
} }
.into()
} }
_ => "".to_string(), _ => "".into(),
}; };
debug!(sl!(), "using option {:?}={:?} ({})", name, value, msg); 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 // Store auto-generated value
options.insert(name.to_string(), value.to_string()); options.insert(name.to_string(), value.to_string());
value Ok(value)
} }
pub fn generate_random_hex_string(len: u32) -> String { 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<String> { fn config_file_from_bundle_dir(bundle_dir: &str) -> Result<String> {
if bundle_dir == "" { if bundle_dir.is_empty() {
return Err(anyhow!("missing bundle directory")); return Err(anyhow!("missing bundle directory"));
} }
@ -739,18 +734,20 @@ fn oci_to_ttrpc(bundle_dir: &str, cid: &str, oci: &ociSpec) -> Result<ttrpcSpec>
Ok(ttrpc_spec) Ok(ttrpc_spec)
} }
fn uri_to_filename(uri: &str) -> Result<String> { // Split a URI and return a tuple comprising the scheme and the data.
if !uri.starts_with(FILE_URI) { //
return Err(anyhow!(format!("invalid URI: {:?}", uri))); // 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 { 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<String> { pub fn spec_file_to_string(spec_file: String) -> Result<String> {
@ -766,9 +763,9 @@ pub fn get_oci_spec_json(cfg: &Config) -> Result<String> {
} }
pub fn get_ttrpc_spec(options: &mut Options, cid: &str) -> Result<ttrpcSpec> { pub fn get_ttrpc_spec(options: &mut Options, cid: &str) -> Result<ttrpcSpec> {
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, ""); assert_ne!(json_spec, "");
let oci_spec: ociSpec = serde_json::from_str(&json_spec).map_err(|e| anyhow!(e))?; 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<Vec<u8>> {
Ok(s.as_bytes().to_vec()) 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<T: Default + DeserializeOwned>(args: &str) -> Result<T> {
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()),
}
}