libs: enhance kata-sys-util

1. move verify_cid from agent to libs/kata-sys-util
2. enhance kata-sys-util/k8s

Signed-off-by: Quanwei Zhou <quanweiZhou@linux.alibaba.com>
This commit is contained in:
Quanwei Zhou 2022-03-03 14:24:08 +08:00 committed by Fupan Li
parent 69ba1ae9e4
commit 641b736106
10 changed files with 495 additions and 277 deletions

76
src/agent/Cargo.lock generated
View File

@ -214,6 +214,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "common-path"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101"
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.3" version = "0.8.3"
@ -311,6 +317,17 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "fail"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3245a0ca564e7f3c797d20d833a6870f57a728ac967d5225b3ffdef4465011"
dependencies = [
"lazy_static",
"log",
"rand",
]
[[package]] [[package]]
name = "fixedbitset" name = "fixedbitset"
version = "0.2.0" version = "0.2.0"
@ -440,6 +457,12 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.11.2" version = "0.11.2"
@ -571,6 +594,7 @@ dependencies = [
"clap", "clap",
"futures", "futures",
"ipnetwork", "ipnetwork",
"kata-sys-util",
"lazy_static", "lazy_static",
"libc", "libc",
"log", "log",
@ -608,6 +632,44 @@ dependencies = [
"vsock-exporter", "vsock-exporter",
] ]
[[package]]
name = "kata-sys-util"
version = "0.1.0"
dependencies = [
"cgroups-rs",
"chrono",
"common-path",
"fail",
"kata-types",
"lazy_static",
"libc",
"nix 0.23.1",
"oci",
"once_cell",
"serde_json",
"slog",
"slog-scope",
"subprocess",
"thiserror",
]
[[package]]
name = "kata-types"
version = "0.1.0"
dependencies = [
"glob",
"lazy_static",
"num_cpus",
"oci",
"regex",
"serde",
"serde_json",
"slog",
"slog-scope",
"thiserror",
"toml",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -863,9 +925,9 @@ dependencies = [
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.13.0" version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi",
"libc", "libc",
@ -1552,6 +1614,16 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subprocess"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "055cf3ebc2981ad8f0a5a17ef6652f652d87831f79fddcba2ac57bcb9a0aa407"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.82" version = "1.0.82"

View File

@ -20,6 +20,7 @@ scopeguard = "1.0.0"
thiserror = "1.0.26" thiserror = "1.0.26"
regex = "1.5.4" regex = "1.5.4"
serial_test = "0.5.1" serial_test = "0.5.1"
kata-sys-util = { path = "../libs/kata-sys-util" }
sysinfo = "0.23.0" sysinfo = "0.23.0"
# Async helpers # Async helpers

View File

@ -134,30 +134,6 @@ pub struct AgentService {
sandbox: Arc<Mutex<Sandbox>>, sandbox: Arc<Mutex<Sandbox>>,
} }
// A container ID must match this regex:
//
// ^[a-zA-Z0-9][a-zA-Z0-9_.-]+$
//
fn verify_cid(id: &str) -> Result<()> {
let mut chars = id.chars();
let valid = match chars.next() {
Some(first)
if first.is_alphanumeric()
&& id.len() > 1
&& chars.all(|c| c.is_alphanumeric() || ['.', '-', '_'].contains(&c)) =>
{
true
}
_ => false,
};
match valid {
true => Ok(()),
false => Err(anyhow!("invalid container ID: {:?}", id)),
}
}
impl AgentService { impl AgentService {
#[instrument] #[instrument]
async fn do_create_container( async fn do_create_container(
@ -166,7 +142,7 @@ impl AgentService {
) -> Result<()> { ) -> Result<()> {
let cid = req.container_id.clone(); let cid = req.container_id.clone();
verify_cid(&cid)?; kata_sys_util::validate::verify_cid(&cid)?;
let mut oci_spec = req.OCI.clone(); let mut oci_spec = req.OCI.clone();
let use_sandbox_pidns = req.get_sandbox_pidns(); let use_sandbox_pidns = req.get_sandbox_pidns();
@ -2674,233 +2650,6 @@ OtherField:other
} }
} }
#[tokio::test]
async fn test_verify_cid() {
#[derive(Debug)]
struct TestData<'a> {
id: &'a str,
expect_error: bool,
}
let tests = &[
TestData {
// Cannot be blank
id: "",
expect_error: true,
},
TestData {
// Cannot be a space
id: " ",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: ".",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "-",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "_",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: " a",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: ".a",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "-a",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "_a",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "..",
expect_error: true,
},
TestData {
// Too short
id: "a",
expect_error: true,
},
TestData {
// Too short
id: "z",
expect_error: true,
},
TestData {
// Too short
id: "A",
expect_error: true,
},
TestData {
// Too short
id: "Z",
expect_error: true,
},
TestData {
// Too short
id: "0",
expect_error: true,
},
TestData {
// Too short
id: "9",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "-1",
expect_error: true,
},
TestData {
id: "/",
expect_error: true,
},
TestData {
id: "a/",
expect_error: true,
},
TestData {
id: "a/../",
expect_error: true,
},
TestData {
id: "../a",
expect_error: true,
},
TestData {
id: "../../a",
expect_error: true,
},
TestData {
id: "../../../a",
expect_error: true,
},
TestData {
id: "foo/../bar",
expect_error: true,
},
TestData {
id: "foo bar",
expect_error: true,
},
TestData {
id: "a.",
expect_error: false,
},
TestData {
id: "a..",
expect_error: false,
},
TestData {
id: "aa",
expect_error: false,
},
TestData {
id: "aa.",
expect_error: false,
},
TestData {
id: "hello..world",
expect_error: false,
},
TestData {
id: "hello/../world",
expect_error: true,
},
TestData {
id: "aa1245124sadfasdfgasdga.",
expect_error: false,
},
TestData {
id: "aAzZ0123456789_.-",
expect_error: false,
},
TestData {
id: "abcdefghijklmnopqrstuvwxyz0123456789.-_",
expect_error: false,
},
TestData {
id: "0123456789abcdefghijklmnopqrstuvwxyz.-_",
expect_error: false,
},
TestData {
id: " abcdefghijklmnopqrstuvwxyz0123456789.-_",
expect_error: true,
},
TestData {
id: ".abcdefghijklmnopqrstuvwxyz0123456789.-_",
expect_error: true,
},
TestData {
id: "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_",
expect_error: false,
},
TestData {
id: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.-_",
expect_error: false,
},
TestData {
id: " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_",
expect_error: true,
},
TestData {
id: ".ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_",
expect_error: true,
},
TestData {
id: "/a/b/c",
expect_error: true,
},
TestData {
id: "a/b/c",
expect_error: true,
},
TestData {
id: "foo/../../../etc/passwd",
expect_error: true,
},
TestData {
id: "../../../../../../etc/motd",
expect_error: true,
},
TestData {
id: "/etc/passwd",
expect_error: true,
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d);
let result = verify_cid(d.id);
let msg = format!("{}, result: {:?}", msg, result);
if result.is_ok() {
assert!(!d.expect_error, "{}", msg);
} else {
assert!(d.expect_error, "{}", msg);
}
}
}
#[tokio::test] #[tokio::test]
async fn test_volume_capacity_stats() { async fn test_volume_capacity_stats() {
skip_if_not_root!(); skip_if_not_root!();

View File

@ -13,8 +13,16 @@ use std::process::Command;
use crate::{eother, sl}; use crate::{eother, sl};
// nix filesystem_type for different target_os
#[cfg(all(target_os = "linux", target_env = "musl"))]
type FsType = libc::c_ulong;
#[cfg(all(target_os = "linux", not(any(target_env = "musl"))))]
type FsType = libc::__fsword_t;
// from linux.git/fs/fuse/inode.c: #define FUSE_SUPER_MAGIC 0x65735546 // from linux.git/fs/fuse/inode.c: #define FUSE_SUPER_MAGIC 0x65735546
const FUSE_SUPER_MAGIC: u32 = 0x65735546; const FUSE_SUPER_MAGIC: FsType = 0x65735546;
// from linux.git/include/uapi/linux/magic.h
const OVERLAYFS_SUPER_MAGIC: FsType = 0x794c7630;
/// Get bundle path (current working directory). /// Get bundle path (current working directory).
pub fn get_bundle_path() -> Result<PathBuf> { pub fn get_bundle_path() -> Result<PathBuf> {
@ -35,7 +43,7 @@ pub fn get_base_name<P: AsRef<Path>>(src: P) -> Result<OsString> {
/// Check whether `path` is on a fuse filesystem. /// Check whether `path` is on a fuse filesystem.
pub fn is_fuse_fs<P: AsRef<Path>>(path: P) -> bool { pub fn is_fuse_fs<P: AsRef<Path>>(path: P) -> bool {
if let Ok(st) = nix::sys::statfs::statfs(path.as_ref()) { if let Ok(st) = nix::sys::statfs::statfs(path.as_ref()) {
if st.filesystem_type().0 == FUSE_SUPER_MAGIC as i64 { if st.filesystem_type().0 == FUSE_SUPER_MAGIC {
return true; return true;
} }
} }
@ -45,7 +53,7 @@ pub fn is_fuse_fs<P: AsRef<Path>>(path: P) -> bool {
/// Check whether `path` is on a overlay filesystem. /// Check whether `path` is on a overlay filesystem.
pub fn is_overlay_fs<P: AsRef<Path>>(path: P) -> bool { pub fn is_overlay_fs<P: AsRef<Path>>(path: P) -> bool {
if let Ok(st) = nix::sys::statfs::statfs(path.as_ref()) { if let Ok(st) = nix::sys::statfs::statfs(path.as_ref()) {
if st.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC { if st.filesystem_type().0 == OVERLAYFS_SUPER_MAGIC {
return true; return true;
} }
} }

View File

@ -13,6 +13,7 @@ pub mod hooks;
pub mod k8s; pub mod k8s;
pub mod mount; pub mod mount;
pub mod numa; pub mod numa;
pub mod validate;
// Convenience macro to obtain the scoped logger // Convenience macro to obtain the scoped logger
#[macro_export] #[macro_export]

View File

@ -0,0 +1,267 @@
// Copyright (c) 2019-2022 Alibaba Cloud
// Copyright (c) 2019-2022 Ant Group
//
// SPDX-License-Identifier: Apache-2.0
//
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("invalid container ID {0}")]
InvalidContainerID(String),
}
// A container ID must match this regex:
//
// ^[a-zA-Z0-9][a-zA-Z0-9_.-]+$
//
pub fn verify_cid(id: &str) -> Result<(), Error> {
let mut chars = id.chars();
let valid = match chars.next() {
Some(first)
if first.is_alphanumeric()
&& id.len() > 1
&& chars.all(|c| c.is_alphanumeric() || ['.', '-', '_'].contains(&c)) =>
{
true
}
_ => false,
};
match valid {
true => Ok(()),
false => Err(Error::InvalidContainerID(id.to_string())),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verify_cid() {
#[derive(Debug)]
struct TestData<'a> {
id: &'a str,
expect_error: bool,
}
let tests = &[
TestData {
// Cannot be blank
id: "",
expect_error: true,
},
TestData {
// Cannot be a space
id: " ",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: ".",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "-",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "_",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: " a",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: ".a",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "-a",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "_a",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "..",
expect_error: true,
},
TestData {
// Too short
id: "a",
expect_error: true,
},
TestData {
// Too short
id: "z",
expect_error: true,
},
TestData {
// Too short
id: "A",
expect_error: true,
},
TestData {
// Too short
id: "Z",
expect_error: true,
},
TestData {
// Too short
id: "0",
expect_error: true,
},
TestData {
// Too short
id: "9",
expect_error: true,
},
TestData {
// Must start with an alphanumeric
id: "-1",
expect_error: true,
},
TestData {
id: "/",
expect_error: true,
},
TestData {
id: "a/",
expect_error: true,
},
TestData {
id: "a/../",
expect_error: true,
},
TestData {
id: "../a",
expect_error: true,
},
TestData {
id: "../../a",
expect_error: true,
},
TestData {
id: "../../../a",
expect_error: true,
},
TestData {
id: "foo/../bar",
expect_error: true,
},
TestData {
id: "foo bar",
expect_error: true,
},
TestData {
id: "a.",
expect_error: false,
},
TestData {
id: "a..",
expect_error: false,
},
TestData {
id: "aa",
expect_error: false,
},
TestData {
id: "aa.",
expect_error: false,
},
TestData {
id: "hello..world",
expect_error: false,
},
TestData {
id: "hello/../world",
expect_error: true,
},
TestData {
id: "aa1245124sadfasdfgasdga.",
expect_error: false,
},
TestData {
id: "aAzZ0123456789_.-",
expect_error: false,
},
TestData {
id: "abcdefghijklmnopqrstuvwxyz0123456789.-_",
expect_error: false,
},
TestData {
id: "0123456789abcdefghijklmnopqrstuvwxyz.-_",
expect_error: false,
},
TestData {
id: " abcdefghijklmnopqrstuvwxyz0123456789.-_",
expect_error: true,
},
TestData {
id: ".abcdefghijklmnopqrstuvwxyz0123456789.-_",
expect_error: true,
},
TestData {
id: "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_",
expect_error: false,
},
TestData {
id: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.-_",
expect_error: false,
},
TestData {
id: " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_",
expect_error: true,
},
TestData {
id: ".ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_",
expect_error: true,
},
TestData {
id: "/a/b/c",
expect_error: true,
},
TestData {
id: "a/b/c",
expect_error: true,
},
TestData {
id: "foo/../../../etc/passwd",
expect_error: true,
},
TestData {
id: "../../../../../../etc/motd",
expect_error: true,
},
TestData {
id: "/etc/passwd",
expect_error: true,
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d);
let result = verify_cid(d.id);
let msg = format!("{}, result: {:?}", msg, result);
if result.is_ok() {
assert!(!d.expect_error, "{}", msg);
} else {
assert!(d.expect_error, "{}", msg);
}
}
}
}

1
src/libs/kata-types/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
Cargo.lock

View File

@ -7,11 +7,17 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
const CONTAINER: &str = "container"; // a container running within a pod
const SANDBOX: &str = "sandbox"; pub(crate) const POD_CONTAINER: &str = "pod_container";
const POD_CONTAINER: &str = "pod_container"; // cri containerd/crio/docker: a container running within a pod
const POD_SANDBOX: &str = "pod_sandbox"; pub(crate) const CONTAINER: &str = "container";
const POD_SANDBOX2: &str = "podsandbox";
// a pod sandbox container
pub(crate) const POD_SANDBOX: &str = "pod_sandbox";
// cri containerd/crio: a pod sandbox container
pub(crate) const SANDBOX: &str = "sandbox";
// docker: a sandbox sandbox container
pub(crate) const PODSANDBOX: &str = "podsandbox";
const STATE_READY: &str = "ready"; const STATE_READY: &str = "ready";
const STATE_RUNNING: &str = "running"; const STATE_RUNNING: &str = "running";
@ -68,7 +74,7 @@ impl FromStr for ContainerType {
fn from_str(value: &str) -> Result<Self, Self::Err> { fn from_str(value: &str) -> Result<Self, Self::Err> {
match value { match value {
POD_CONTAINER | CONTAINER => Ok(ContainerType::PodContainer), POD_CONTAINER | CONTAINER => Ok(ContainerType::PodContainer),
POD_SANDBOX | POD_SANDBOX2 | SANDBOX => Ok(ContainerType::PodSandbox), POD_SANDBOX | PODSANDBOX | SANDBOX => Ok(ContainerType::PodSandbox),
_ => Err(Error::InvalidContainerType(value.to_owned())), _ => Err(Error::InvalidContainerType(value.to_owned())),
} }
} }

View File

@ -56,10 +56,10 @@ pub fn container_type(spec: &oci::Spec) -> ContainerType {
/// Determine the k8s sandbox ID from OCI annotations. /// Determine the k8s sandbox ID from OCI annotations.
/// ///
/// This function is expected to be called only when the container type is "PodContainer". /// This function is expected to be called only when the container type is "PodContainer".
pub fn sandbox_id(spec: &oci::Spec) -> Result<Option<String>, String> { pub fn container_type_with_id(spec: &oci::Spec) -> (ContainerType, Option<String>) {
if container_type(spec) != ContainerType::PodSandbox { let container_type = container_type(spec);
return Err("Not a sandbox container".to_string()); let mut sid = None;
} if container_type == ContainerType::PodContainer {
for k in [ for k in [
annotations::crio::SANDBOX_ID_LABEL_KEY, annotations::crio::SANDBOX_ID_LABEL_KEY,
annotations::cri_containerd::SANDBOX_ID_LABEL_KEY, annotations::cri_containerd::SANDBOX_ID_LABEL_KEY,
@ -68,16 +68,19 @@ pub fn sandbox_id(spec: &oci::Spec) -> Result<Option<String>, String> {
.iter() .iter()
{ {
if let Some(id) = spec.annotations.get(k.to_owned()) { if let Some(id) = spec.annotations.get(k.to_owned()) {
return Ok(Some(id.to_string())); sid = Some(id.to_string());
break;
}
} }
} }
Ok(None) (container_type, sid)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{annotations, container};
#[test] #[test]
fn test_is_empty_dir() { fn test_is_empty_dir() {
@ -99,4 +102,112 @@ mod tests {
let empty_dir = "/kubernetes.io~empty-dir/shm"; let empty_dir = "/kubernetes.io~empty-dir/shm";
assert!(is_empty_dir(empty_dir)); assert!(is_empty_dir(empty_dir));
} }
#[test]
fn test_container_type() {
let sid = "sid".to_string();
let mut spec = oci::Spec::default();
// default
assert_eq!(
container_type_with_id(&spec),
(ContainerType::PodSandbox, None)
);
// crio sandbox
spec.annotations = [(
annotations::crio::CONTAINER_TYPE_LABEL_KEY.to_string(),
container::SANDBOX.to_string(),
)]
.iter()
.cloned()
.collect();
assert_eq!(
container_type_with_id(&spec),
(ContainerType::PodSandbox, None)
);
// cri containerd sandbox
spec.annotations = [(
annotations::crio::CONTAINER_TYPE_LABEL_KEY.to_string(),
container::POD_SANDBOX.to_string(),
)]
.iter()
.cloned()
.collect();
assert_eq!(
container_type_with_id(&spec),
(ContainerType::PodSandbox, None)
);
// docker shim sandbox
spec.annotations = [(
annotations::crio::CONTAINER_TYPE_LABEL_KEY.to_string(),
container::PODSANDBOX.to_string(),
)]
.iter()
.cloned()
.collect();
assert_eq!(
container_type_with_id(&spec),
(ContainerType::PodSandbox, None)
);
// crio container
spec.annotations = [
(
annotations::crio::CONTAINER_TYPE_LABEL_KEY.to_string(),
container::CONTAINER.to_string(),
),
(
annotations::crio::SANDBOX_ID_LABEL_KEY.to_string(),
sid.clone(),
),
]
.iter()
.cloned()
.collect();
assert_eq!(
container_type_with_id(&spec),
(ContainerType::PodContainer, Some(sid.clone()))
);
// cri containerd container
spec.annotations = [
(
annotations::cri_containerd::CONTAINER_TYPE_LABEL_KEY.to_string(),
container::POD_CONTAINER.to_string(),
),
(
annotations::cri_containerd::SANDBOX_ID_LABEL_KEY.to_string(),
sid.clone(),
),
]
.iter()
.cloned()
.collect();
assert_eq!(
container_type_with_id(&spec),
(ContainerType::PodContainer, Some(sid.clone()))
);
// docker shim container
spec.annotations = [
(
annotations::dockershim::CONTAINER_TYPE_LABEL_KEY.to_string(),
container::CONTAINER.to_string(),
),
(
annotations::dockershim::SANDBOX_ID_LABEL_KEY.to_string(),
sid.clone(),
),
]
.iter()
.cloned()
.collect();
assert_eq!(
container_type_with_id(&spec),
(ContainerType::PodContainer, Some(sid))
);
}
} }

View File

@ -1,9 +1,11 @@
Cargo.lock Cargo.lock
src/agent.rs src/agent.rs
src/agent_ttrpc.rs src/agent_ttrpc.rs
src/agent_ttrpc_async.rs
src/csi.rs src/csi.rs
src/empty.rs src/empty.rs
src/health.rs src/health.rs
src/health_ttrpc.rs src/health_ttrpc.rs
src/health_ttrpc_async.rs
src/oci.rs src/oci.rs
src/types.rs src/types.rs