use std::{future::Future, path::PathBuf, pin::Pin, str::FromStr};
use crate::{
error::Error,
types::{
Args, CNIResult, ErrorResult, NetConf, CNI_ARGS, CNI_COMMAND, CNI_CONTAINERID, CNI_IFNAME,
CNI_NETNS, CNI_PATH,
},
util::{get_env, IoTarget},
version::PluginInfo,
};
pub struct Plugin {
add: CmdFn,
del: CmdFn,
check: CmdFn,
version_info: PluginInfo,
about: String,
io: IoTarget,
}
impl Plugin {
pub fn new(
add: CmdFn,
del: CmdFn,
check: CmdFn,
version_info: PluginInfo,
about: &str,
) -> Plugin {
Plugin {
add,
del,
check,
version_info,
about: about.to_string(),
io: IoTarget::default(),
}
}
pub async fn run(&mut self) -> Result<(), Error> {
match self.inner_run(get_env).await {
Ok(res) => {
self.io
.stdout
.write(res.as_bytes())
.map_err(|e| Error::IOFailure(e.to_string()))?;
Ok(())
}
Err(e) => {
let res = self.error_result(&e);
let data =
serde_json::to_vec(&res).map_err(|e| Error::FailedToDecode(e.to_string()))?;
self.io
.stdout
.write(&data)
.map_err(|e| Error::FailedToDecode(e.to_string()))?;
Err(e)
}
}
}
async fn inner_run<'a>(
&mut self,
get_env: impl Fn(&'a str) -> Result<String, Error>,
) -> Result<String, Error> {
let cmd = self.get_cmd(get_env)?;
cmd.run().await
}
fn get_cmd<'a>(
&mut self,
get_env: impl Fn(&'a str) -> Result<String, Error>,
) -> Result<Cmd, Error> {
let cmd_str = get_env(CNI_COMMAND).unwrap_or_default();
match cmd_str.as_str() {
"ADD" => {
let container_id = get_env(CNI_CONTAINERID)?;
let ifname = get_env(CNI_IFNAME)?;
let netns = PathBuf::from_str(&get_env(CNI_NETNS)?)
.map_err(|e| Error::InvalidEnvValue(e.to_string()))?;
let paths = get_env(CNI_PATH)?;
let path = paths.split(':').map(PathBuf::from).collect();
let args = get_env(CNI_ARGS).ok();
let mut buf = String::new();
self.io
.stdin
.read_to_string(&mut buf)
.map_err(|e| Error::IOFailure(e.to_string()))?;
self.version_info.validate(&buf)?;
let config: NetConf =
serde_json::from_str(&buf).map_err(|e| Error::FailedToDecode(e.to_string()))?;
Ok(Cmd::Add {
f: self.add,
args: Args {
container_id,
netns: Some(netns),
ifname,
args,
path,
config: Some(config),
},
})
}
"DEL" => {
let container_id = get_env(CNI_CONTAINERID)?;
let ifname = get_env(CNI_IFNAME)?;
let netns = match get_env(CNI_NETNS).as_ref().ok() {
Some(netns) => Some(
PathBuf::from_str(netns)
.map_err(|e| Error::InvalidEnvValue(e.to_string()))?,
),
None => None,
};
let paths = get_env(CNI_PATH)?;
let path = paths.split(':').map(PathBuf::from).collect();
let args = get_env(CNI_ARGS).ok();
let mut buf = String::new();
self.io
.stdin
.read_to_string(&mut buf)
.map_err(|e| Error::IOFailure(e.to_string()))?;
self.version_info.validate(&buf)?;
let config: NetConf =
serde_json::from_str(&buf).map_err(|e| Error::FailedToDecode(e.to_string()))?;
Ok(Cmd::Del {
f: self.del,
args: Args {
container_id,
netns,
ifname,
args,
path,
config: Some(config),
},
})
}
"CHECK" => {
let container_id = get_env(CNI_CONTAINERID)?;
let ifname = get_env(CNI_IFNAME)?;
let netns = PathBuf::from_str(&get_env(CNI_NETNS)?)
.map_err(|e| Error::InvalidEnvValue(e.to_string()))?;
let paths = get_env(CNI_PATH)?;
let path = paths.split(':').map(PathBuf::from).collect();
let args = get_env(CNI_ARGS).ok();
let mut buf = String::new();
self.io
.stdin
.read_to_string(&mut buf)
.map_err(|e| Error::IOFailure(e.to_string()))?;
self.version_info.validate(&buf)?;
let config: NetConf =
serde_json::from_str(&buf).map_err(|e| Error::FailedToDecode(e.to_string()))?;
Ok(Cmd::Check {
f: self.check,
args: Args {
container_id,
netns: Some(netns),
ifname,
args,
path,
config: Some(config),
},
})
}
"VERSION" => Ok(Cmd::Version(self.version_info.clone())),
_ => Ok(Cmd::About(self.version_info.clone(), self.about.clone())),
}
}
fn error_result(&self, err: &Error) -> ErrorResult {
ErrorResult {
cni_version: self.version_info.cni_version.clone(),
code: err.into(),
msg: err.to_string(),
details: err.details(),
}
}
}
enum Cmd {
Add { f: CmdFn, args: Args },
Del { f: CmdFn, args: Args },
Check { f: CmdFn, args: Args },
Version(PluginInfo),
About(PluginInfo, String),
}
pub type CmdFn = fn(Args) -> Pin<Box<dyn Future<Output = Result<CNIResult, Error>>>>;
impl Cmd {
async fn run(&self) -> Result<String, Error> {
match self {
Cmd::Add { f, args } | Cmd::Del { f, args } | Cmd::Check { f, args } => {
match f(args.clone()).await {
Ok(res) => serde_json::to_string(&res)
.map_err(|e| Error::FailedToDecode(format!("{e}: {:?}", args.config))),
Err(e) => Err(e),
}
}
Cmd::Version(info) => {
let out = info.version()?;
Ok(out)
}
Cmd::About(info, about) => {
let out = info.about(about)?;
Ok(out)
}
}
}
}
impl From<Cmd> for &str {
fn from(value: Cmd) -> Self {
match value {
Cmd::Add { .. } => "ADD",
Cmd::Del { .. } => "DEL",
Cmd::Check { .. } => "CHECK",
Cmd::Version(_) => "VERSION",
Cmd::About(_, _) => "NOP",
}
}
}