keyfork/crates/keyfork/src/cli/provision/mod.rs

151 lines
4.2 KiB
Rust

use super::Keyfork;
use crate::config;
use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum};
#[derive(Debug, Clone)]
pub enum Provisioner {
OpenPGPCard(OpenPGPCard),
}
impl std::fmt::Display for Provisioner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Provisioner::OpenPGPCard(_) => f.write_str("openpgp-card"),
}
}
}
impl Provisioner {
fn discover(&self) -> Vec<(String, Option<String>)> {
match self {
Provisioner::OpenPGPCard(o) => o.discover(),
}
}
fn provision(
&self,
provisioner: config::Provisioner,
) -> Result<(), Box<dyn std::error::Error>> {
match self {
Provisioner::OpenPGPCard(o) => o.provision(provisioner),
}
}
}
impl ValueEnum for Provisioner {
fn value_variants<'a>() -> &'a [Self] {
&[Self::OpenPGPCard(OpenPGPCard)]
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(PossibleValue::new(match self {
Self::OpenPGPCard(_) => "openpgp-card",
}))
}
}
trait ProvisionExec {
/// Discover all known places the formatted key can be deployed to.
fn discover(&self) -> Vec<(String, Option<String>)> {
vec![]
}
/// Derive a key and deploy it to a target.
fn provision(&self, p: config::Provisioner) -> Result<(), Box<dyn std::error::Error>>;
}
#[derive(Clone, Debug)]
pub struct OpenPGPCard;
impl ProvisionExec for OpenPGPCard {
fn discover(&self) -> Vec<(String, Option<String>)> {
/*
vec![
(
"0006:26144195".to_string(),
Some("Yubicats Heywood".to_string()),
),
(
"0006:2614419y".to_string(),
Some("Yubicats Heywood".to_string()),
),
]
*/
vec![]
}
fn provision(&self, _p: config::Provisioner) -> Result<(), Box<dyn std::error::Error>> {
todo!()
}
}
#[derive(Subcommand, Clone, Debug)]
pub enum ProvisionSubcommands {
/// Discover all available targets on the system.
Discover,
}
// NOTE: All struct fields are marked as Option so they may be constructed when running a
// subcommand. This is allowed through marking the parent command subcommand_negates_reqs(true).
#[derive(Parser, Debug, Clone)]
pub struct Provision {
#[command(subcommand)]
pub subcommand: Option<ProvisionSubcommands>,
provisioner_name: Provisioner,
/// Account ID.
#[arg(long, required(true))]
account_id: Option<u32>,
/// Identifier of the hardware to deploy to, listable by running the `discover` subcommand.
#[arg(long, required(true))]
identifier: Option<String>,
}
// NOTE: In the future, this impl will be used by `keyfork recover` to reprovision hardware from
// the config file, iterating through a [Config::provisioner].
// TODO: How can metadata be passed in?
#[derive(thiserror::Error, Debug)]
#[error("Missing field: {0}")]
pub struct MissingField(&'static str);
impl TryFrom<Provision> for config::Provisioner {
type Error = MissingField;
fn try_from(value: Provision) -> Result<Self, Self::Error> {
Ok(Self {
name: value.provisioner_name.to_string(),
account: value.account_id.ok_or(MissingField("account_id"))?,
identifier: value.identifier.ok_or(MissingField("identifier"))?,
metadata: Default::default(),
})
}
}
impl Provision {
pub fn handle(&self, _keyfork: &Keyfork) -> Result<(), Box<dyn std::error::Error>> {
match self.subcommand {
Some(ProvisionSubcommands::Discover) => {
let mut iter = self.provisioner_name.discover().into_iter().peekable();
while let Some((identifier, context)) = iter.next() {
println!("Identifier: {identifier}");
if let Some(context) = context {
println!("Context: {context}");
}
if iter.peek().is_some() {
println!("---");
}
}
}
None => {
self.provisioner_name.provision(self.clone().try_into()?)?;
}
}
Ok(())
}
}