mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-05-03 14:07:24 +00:00
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:
parent
18c47fe8f3
commit
8ab90e1068
49
src/agent/Cargo.lock
generated
49
src/agent/Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -3,29 +3,148 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::fs;
|
||||
use ttrpc_codegen::{Codegen, Customize};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use ttrpc_codegen::{Codegen, Customize, ProtobufCustomize};
|
||||
|
||||
fn replace_text_in_file(file_name: &str, from: &str, to: &str) -> Result<(), std::io::Error> {
|
||||
let mut src = File::open(file_name)?;
|
||||
let mut contents = String::new();
|
||||
src.read_to_string(&mut contents).unwrap();
|
||||
drop(src);
|
||||
|
||||
let new_contents = contents.replace(from, to);
|
||||
|
||||
let mut dst = File::create(&file_name)?;
|
||||
dst.write_all(new_contents.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn use_serde(protos: &[&str], out_dir: &Path) -> Result<(), std::io::Error> {
|
||||
protos
|
||||
.iter()
|
||||
.try_for_each(|f: &&str| -> Result<(), std::io::Error> {
|
||||
let out_file = Path::new(f)
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.ok_or(format!("failed to get proto file name for {:?}", f))
|
||||
.map(|s| {
|
||||
let t = s.replace(".proto", ".rs");
|
||||
out_dir.join(t)
|
||||
})
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
|
||||
.to_str()
|
||||
.ok_or(format!("cannot convert {:?} path to string", f))
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
|
||||
.to_string();
|
||||
|
||||
replace_text_in_file(
|
||||
&out_file,
|
||||
"derive(Serialize, Deserialize)",
|
||||
"derive(serde::Serialize, serde::Deserialize)",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_file(autogen_comment: &str, rust_filename: &str) -> Result<(), std::io::Error> {
|
||||
let mut new_contents = Vec::new();
|
||||
|
||||
let file = File::open(rust_filename)?;
|
||||
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
// Guard the code since it is only needed for the agent-ctl tool,
|
||||
// not the agent itself.
|
||||
let serde_default_code = r#"#[cfg_attr(feature = "with-serde", serde(default))]"#;
|
||||
|
||||
for line in reader.lines() {
|
||||
let line = line?;
|
||||
|
||||
new_contents.push(line.clone());
|
||||
|
||||
let pattern = "//! Generated file from";
|
||||
|
||||
if line.starts_with(&pattern) {
|
||||
new_contents.push(autogen_comment.into());
|
||||
}
|
||||
|
||||
let struct_pattern = "pub struct ";
|
||||
|
||||
// Although we've requested serde support via `Customize`, to
|
||||
// allow the `kata-agent-ctl` tool to partially deserialise structures
|
||||
// specified in JSON, we need this bit of additional magic.
|
||||
if line.starts_with(&struct_pattern) {
|
||||
new_contents.insert(new_contents.len() - 1, serde_default_code.trim().into());
|
||||
}
|
||||
}
|
||||
|
||||
let data = new_contents.join("\n");
|
||||
|
||||
let mut dst = File::create(&rust_filename)?;
|
||||
|
||||
dst.write_all(data.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn real_main() -> Result<(), std::io::Error> {
|
||||
let autogen_comment = format!("\n//! Generated by {:?} ({:?})", file!(), module_path!());
|
||||
|
||||
fn main() {
|
||||
let protos = vec![
|
||||
"protos/types.proto",
|
||||
"protos/agent.proto",
|
||||
"protos/health.proto",
|
||||
"protos/google/protobuf/empty.proto",
|
||||
"protos/health.proto",
|
||||
"protos/oci.proto",
|
||||
"protos/types.proto",
|
||||
];
|
||||
|
||||
// Tell Cargo that if the .proto files changed, to rerun this build script.
|
||||
protos
|
||||
.iter()
|
||||
.for_each(|p| println!("cargo:rerun-if-changed={}", &p));
|
||||
|
||||
let ttrpc_options = Customize {
|
||||
async_server: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let protobuf_options = ProtobufCustomize {
|
||||
serde_derive: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let out_dir = Path::new("src");
|
||||
|
||||
Codegen::new()
|
||||
.out_dir("src")
|
||||
.out_dir(out_dir)
|
||||
.inputs(&protos)
|
||||
.include("protos")
|
||||
.customize(ttrpc_options)
|
||||
.rust_protobuf()
|
||||
.customize(Customize {
|
||||
async_server: true,
|
||||
..Default::default()
|
||||
})
|
||||
.run()
|
||||
.expect("Gen codes failed.");
|
||||
.rust_protobuf_customize(protobuf_options)
|
||||
.run()?;
|
||||
|
||||
for file in protos.iter() {
|
||||
let proto_filename = Path::new(file).file_name().unwrap();
|
||||
|
||||
let generated_file = proto_filename
|
||||
.to_str()
|
||||
.ok_or("failed")
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
|
||||
.replace(".proto", ".rs");
|
||||
|
||||
let out_file = out_dir.join(generated_file);
|
||||
|
||||
let out_file_str = out_file
|
||||
.to_str()
|
||||
.ok_or("failed")
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||
|
||||
handle_file(&autogen_comment, out_file_str)?;
|
||||
}
|
||||
|
||||
// There is a message named 'Box' in oci.proto
|
||||
// so there is a struct named 'Box', we should replace Box<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);
|
||||
}
|
||||
}
|
||||
|
18
tools/agent-ctl/Cargo.lock
generated
18
tools/agent-ctl/Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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")
|
||||
|
@ -18,4 +18,5 @@ pub struct Config {
|
||||
pub interactive: bool,
|
||||
pub hybrid_vsock: bool,
|
||||
pub ignore_errors: bool,
|
||||
pub no_auto_values: bool,
|
||||
}
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user