use std::{error, fmt, io};
use std::fs::File;
use std::io::BufReader;
use std::str::FromStr;
use rpki::ca::idexchange;
use rpki::ca::idcert::IdCert;
use rpki::ca::idexchange::{
CaHandle, ChildHandle, ChildRequest, ParentResponse
};
use rpki::repository::resources::{
AsBlocks, Ipv4Blocks, Ipv6Blocks, ResourceSet
};
use crate::api;
use crate::cli::client::KrillClient;
use crate::cli::report::Report;
use crate::commons::httpclient;
#[derive(clap::Subcommand)]
pub enum Command {
Add(Add),
Update(Update),
Info(Info),
Remove(Remove),
Response(Response),
Connections(Connections),
Suspend(Suspend),
Unsuspend(Unsuspend),
}
impl Command {
pub async fn run(self, client: &KrillClient) -> Report {
match self {
Self::Add(cmd) => cmd.run(client).await.into(),
Self::Update(cmd) => cmd.run(client).await.into(),
Self::Info(cmd) => cmd.run(client).await.into(),
Self::Remove(cmd) => cmd.run(client).await.into(),
Self::Response(cmd) => cmd.run(client).await.into(),
Self::Connections(cmd) => cmd.run(client).await.into(),
Self::Suspend(cmd) => cmd.run(client).await.into(),
Self::Unsuspend(cmd) => cmd.run(client).await.into(),
}
}
}
#[derive(clap::Args)]
struct Handle {
#[arg(long, short, env = "KRILL_CLI_MY_CA")]
ca: CaHandle,
#[arg(long, value_name = "name")]
child: ChildHandle,
}
#[derive(clap::Parser)]
pub struct Add {
#[command(flatten)]
handle: Handle,
#[command(flatten)]
resources: ChildResources,
#[arg(long, short, value_name = "path")]
request: ChildIdCert,
}
impl Add {
pub async fn run(
self, client: &KrillClient
) -> Result<ParentResponse, httpclient::Error> {
client.child_add(
&self.handle.ca, self.handle.child,
self.resources.into(), self.request.0
).await
}
}
#[derive(clap::Args)]
pub struct Info {
#[command(flatten)]
handle: Handle,
}
impl Info {
pub async fn run(
self, client: &KrillClient
) -> Result<api::ca::ChildCaInfo, httpclient::Error> {
client.child_details(&self.handle.ca, &self.handle.child).await
}
}
#[derive(clap::Parser)]
pub struct Update {
#[command(flatten)]
handle: Handle,
#[command(flatten)]
resources: ChildResources,
#[arg(long, short, value_name = "path")]
request: Option<ChildIdCert>,
}
impl Update {
pub async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.child_update(&self.handle.ca, &self.handle.child,
api::admin::UpdateChildRequest {
id_cert: self.request.map(|x| x.0),
resources: self.resources.into(),
suspend: None,
resource_class_name_mapping: None,
}
).await
}
}
#[derive(clap::Args)]
pub struct Response {
#[command(flatten)]
handle: Handle,
}
impl Response {
pub async fn run(
self, client: &KrillClient
) -> Result<idexchange::ParentResponse, httpclient::Error> {
client.child_contact(&self.handle.ca, &self.handle.child).await
}
}
#[derive(clap::Args)]
pub struct Suspend {
#[command(flatten)]
handle: Handle,
}
impl Suspend {
pub async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.child_update(
&self.handle.ca, &self.handle.child,
api::admin::UpdateChildRequest::suspend(),
).await
}
}
#[derive(clap::Args)]
pub struct Unsuspend {
#[command(flatten)]
handle: Handle,
}
impl Unsuspend {
pub async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.child_update(
&self.handle.ca, &self.handle.child,
api::admin::UpdateChildRequest::unsuspend(),
).await
}
}
#[derive(clap::Args)]
pub struct Remove {
#[command(flatten)]
handle: Handle,
}
impl Remove {
pub async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.child_delete(&self.handle.ca, &self.handle.child).await
}
}
#[derive(clap::Args)]
pub struct ChildResources {
#[arg(short, long, value_name = "AS resources")]
asn: Option<AsBlocks>,
#[arg(short = '4', long, value_name = "IPv4 resources")]
ipv4: Option<Ipv4Blocks>,
#[arg(short = '6', long, value_name = "IPv6 resources")]
ipv6: Option<Ipv6Blocks>,
}
impl From<ChildResources> for ResourceSet {
fn from(src: ChildResources) -> Self {
ResourceSet::new(
src.asn.unwrap_or_default(),
src.ipv4.unwrap_or_default(),
src.ipv6.unwrap_or_default(),
)
}
}
impl From<ChildResources> for Option<ResourceSet> {
fn from(src: ChildResources) -> Self {
if src.asn.is_none() && src.ipv4.is_none() && src.ipv6.is_none() {
None
}
else {
Some(src.into())
}
}
}
#[derive(clap::Args)]
pub struct Connections {
#[arg(long, short, env = "KRILL_CLI_MY_CA")]
ca: CaHandle,
}
impl Connections {
pub async fn run(
self, client: &KrillClient
) -> Result<api::ca::ChildrenConnectionStats, httpclient::Error> {
client.child_connections(&self.ca).await
}
}
#[derive(Clone, Debug)]
pub struct ChildIdCert(IdCert);
impl FromStr for ChildIdCert {
type Err = ChildIdCertError;
fn from_str(src: &str) -> Result<Self, Self::Err> {
ChildRequest::parse(
BufReader::new(
File::open(src).map_err(|err| {
ChildIdCertError::Io(src.into(), err)
})?
)
).and_then(|req| req.validate()).map(Self).map_err(|err| {
ChildIdCertError::Parse(src.into(), err)
})
}
}
#[derive(Debug)]
pub enum ChildIdCertError {
Io(String, io::Error),
Parse(String, idexchange::Error),
}
impl fmt::Display for ChildIdCertError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Io(path, err) => {
write!(f, "Failed to open request file '{path}': {err}"
)
}
Self::Parse(path, err) => {
write!(f, "Failed to parse request file '{path}': {err}"
)
}
}
}
}
impl error::Error for ChildIdCertError { }