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]]
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"

View File

@ -4,10 +4,16 @@ version = "0.1.0"
authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>"]
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"

View File

@ -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",
];
Codegen::new()
.out_dir("src")
.inputs(&protos)
.include("protos")
.rust_protobuf()
.customize(Customize {
// 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()
})
.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
// 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",
"self: 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> {
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);
}
}

View File

@ -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",

View File

@ -10,7 +10,7 @@ authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>"]
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"

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 -
```
### 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

View File

@ -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<String> {
fn get_agent_cmd_func(name: &str) -> Result<AgentCmdFp> {
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<String> {
fn get_builtin_cmd_func(name: &str) -> Result<BuiltinCmdFp> {
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<ttrpc::Client> {
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,13 +907,17 @@ 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);
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));
let reply = client
@ -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,21 +960,24 @@ 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
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);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -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);
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,12 +1026,13 @@ 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))?;
@ -1017,6 +1054,9 @@ fn agent_cmd_container_exec(
req.set_exec_id(exec_id);
req.set_process(process);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -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);
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);
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);
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);
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);
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,16 +1226,20 @@ 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);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -1198,17 +1259,18 @@ 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 == "" {
if sigstr.is_empty() {
sigstr = DEFAULT_PROC_SIGNAL.to_string();
}
@ -1218,6 +1280,9 @@ fn agent_cmd_container_signal_process(
req.set_exec_id(exec_id);
req.set_signal(signum as u32);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -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,17 +1399,18 @@ 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);
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
@ -1353,7 +1419,7 @@ fn agent_cmd_container_tty_win_resize(
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
@ -1363,6 +1429,9 @@ fn agent_cmd_container_tty_win_resize(
req.set_column(cols);
}
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -1382,16 +1451,20 @@ 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);
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -1411,17 +1484,18 @@ 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);
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
@ -1430,6 +1504,9 @@ fn agent_cmd_container_read_stdout(
req.set_len(length);
}
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -1449,17 +1526,18 @@ 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);
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
@ -1468,6 +1546,9 @@ fn agent_cmd_container_read_stderr(
req.set_len(length);
}
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -1487,20 +1568,24 @@ 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 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());
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -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,16 +1651,17 @@ 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);
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
@ -1585,7 +1671,7 @@ fn agent_cmd_sandbox_copy_file(
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
@ -1595,7 +1681,7 @@ fn agent_cmd_sandbox_copy_file(
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
@ -1605,7 +1691,7 @@ fn agent_cmd_sandbox_copy_file(
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
@ -1615,7 +1701,7 @@ fn agent_cmd_sandbox_copy_file(
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
@ -1624,7 +1710,7 @@ fn agent_cmd_sandbox_copy_file(
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
@ -1633,12 +1719,15 @@ fn agent_cmd_sandbox_copy_file(
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 != "" {
let data = utils::str_to_bytes(&data_str)?;
req.set_data(data.to_vec());
}
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -1658,15 +1747,19 @@ 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);
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());
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -1686,11 +1779,12 @@ 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
@ -1700,7 +1794,7 @@ fn agent_cmd_sandbox_online_cpu_mem(
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
@ -1710,7 +1804,7 @@ fn agent_cmd_sandbox_online_cpu_mem(
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
@ -1720,6 +1814,9 @@ fn agent_cmd_sandbox_online_cpu_mem(
req.set_cpu_only(cpu_only);
}
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -1739,11 +1836,12 @@ 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
@ -1753,7 +1851,7 @@ fn agent_cmd_sandbox_set_guest_date_time(
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
@ -1763,6 +1861,9 @@ fn agent_cmd_sandbox_set_guest_date_time(
req.set_Usec(usecs);
}
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
@ -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,14 +1909,18 @@ 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);
Ok(())
});
// FIXME: Implement fully
eprintln!("FIXME: 'UpdateContainer' not fully implemented");
@ -1838,14 +1943,15 @@ 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 != "" {
run_if_auto_values!(ctx, || -> Result<()> {
if !addr_list.is_empty() {
let addrs: Vec<u64> = addr_list
// Convert into a list of string values.
.split(",")
@ -1865,6 +1971,9 @@ fn agent_cmd_sandbox_mem_hotplug_by_probe(
req.set_memHotplugProbeAddr(addrs);
}
Ok(())
});
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
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
- 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")

View File

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

View File

@ -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<u8> {
if name == "" {
if name.is_empty() {
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
// of nano seconds.
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);
}
@ -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 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<String> {
let words: Vec<&str> = args.split_whitespace().collect();
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 {
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<String> {
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<ttrpcSpec>
Ok(ttrpc_spec)
}
fn uri_to_filename(uri: &str) -> Result<String> {
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<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> {
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<Vec<u8>> {
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()),
}
}