mirror of
https://codeberg.org/tyy/aspm
synced 2024-12-22 20:39:29 -07:00
Add keys delete subcommand
This commit is contained in:
parent
45ad89b06a
commit
bb6bed5531
5 changed files with 148 additions and 12 deletions
41
.vscode/aspm.code-snippets
vendored
Normal file
41
.vscode/aspm.code-snippets
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
// Place your aspm workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||||
|
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||||
|
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||||
|
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||||
|
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||||
|
// Placeholders with the same ids are connected.
|
||||||
|
// Example:
|
||||||
|
// "Print to console": {
|
||||||
|
// "scope": "javascript,typescript",
|
||||||
|
// "prefix": "log",
|
||||||
|
// "body": [
|
||||||
|
// "console.log('$1');",
|
||||||
|
// "$2"
|
||||||
|
// ],
|
||||||
|
// "description": "Log output to console"
|
||||||
|
// }
|
||||||
|
"Create aspm subcommand": {
|
||||||
|
"scope": "rust",
|
||||||
|
"prefix": "subcommand",
|
||||||
|
"body": [
|
||||||
|
"use clap::Parser;",
|
||||||
|
"",
|
||||||
|
"use crate::commands::AspmSubcommand;",
|
||||||
|
"",
|
||||||
|
"/// $2",
|
||||||
|
"#[derive(Parser, Debug)]",
|
||||||
|
"pub struct $1Command { }",
|
||||||
|
"",
|
||||||
|
"#[async_trait::async_trait]",
|
||||||
|
"impl AspmSubcommand for $1Command {",
|
||||||
|
"\tasync fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {",
|
||||||
|
"\t\t",
|
||||||
|
"\t\t",
|
||||||
|
"\t\tOk(())",
|
||||||
|
"\t}",
|
||||||
|
"}",
|
||||||
|
],
|
||||||
|
"description": "Create a template for an aspm subcommand"
|
||||||
|
}
|
||||||
|
}
|
98
src/commands/keys/delete.rs
Normal file
98
src/commands/keys/delete.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
use anstyle::{AnsiColor, Color as AnstyleColor, Reset, Style as Anstyle};
|
||||||
|
use anyhow::Context;
|
||||||
|
use asp::keys::AspKeyType;
|
||||||
|
use clap::Parser;
|
||||||
|
use dialoguer::{Confirm, theme::ColorfulTheme, console::Term};
|
||||||
|
use indoc::writedoc;
|
||||||
|
use sea_orm::ModelTrait;
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
entities::keys::{Entity as KeysEntity, Model as KeysModel},
|
||||||
|
commands::{AspmSubcommand, KeysQueryResult, KeysEntityExt}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Deletes a saved key, after asking for confirmation.
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct KeysDeleteCommand {
|
||||||
|
/// Will disable any confirmation prompts. This is not recommended unless you knowingly give a full fingerprint, as it is easy to accidentally (and permanently) delete wrong keys otherwise.
|
||||||
|
#[arg(long)]
|
||||||
|
no_confirm: bool,
|
||||||
|
/// The key to export. This can either be a fingerprint obtained with the `keys list` command, or an alias to search for.
|
||||||
|
key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl AspmSubcommand for KeysDeleteCommand {
|
||||||
|
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
|
// Fetch key from db
|
||||||
|
let entry = KeysEntity::query_key(&state.db, &self.key)
|
||||||
|
.await
|
||||||
|
.context("Unable to query keys from database")?;
|
||||||
|
let key: KeysModel = match entry {
|
||||||
|
KeysQueryResult::None => {
|
||||||
|
eprintln!(
|
||||||
|
"{style}No keys matching the given query were found{reset}",
|
||||||
|
style = Anstyle::new()
|
||||||
|
.fg_color(Some(AnstyleColor::Ansi(AnsiColor::BrightRed)))
|
||||||
|
.render(),
|
||||||
|
reset = Reset.render()
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
KeysQueryResult::One(key) => key,
|
||||||
|
KeysQueryResult::Many(mut keys) => {
|
||||||
|
eprintln!(
|
||||||
|
"{style}More than one keys matching the given query were found{reset}",
|
||||||
|
style = Anstyle::new()
|
||||||
|
.fg_color(Some(AnstyleColor::Ansi(AnsiColor::BrightYellow)))
|
||||||
|
.render(),
|
||||||
|
reset = Reset::default().render()
|
||||||
|
);
|
||||||
|
keys.remove(0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.no_confirm {
|
||||||
|
// Construct styles
|
||||||
|
let reset = Reset::default().render();
|
||||||
|
let alias_style = Anstyle::new()
|
||||||
|
.underline()
|
||||||
|
.fg_color(Some(anstyle::Color::Ansi(AnsiColor::BrightCyan)))
|
||||||
|
.render();
|
||||||
|
let key_style = Anstyle::new()
|
||||||
|
.fg_color(Some(anstyle::Color::Ansi(AnsiColor::BrightGreen)))
|
||||||
|
.render();
|
||||||
|
let value_style = Anstyle::new()
|
||||||
|
.fg_color(Some(anstyle::Color::Ansi(AnsiColor::BrightYellow)))
|
||||||
|
.render();
|
||||||
|
let _ = writedoc! {
|
||||||
|
std::io::stderr(),
|
||||||
|
"
|
||||||
|
{alias_style}{alias}:{reset}
|
||||||
|
{key_style}Fingerprint{reset} {value_style}{fingerprint}{reset}
|
||||||
|
{key_style}Key Type{reset} {value_style}{key_type:?}{reset}\n
|
||||||
|
",
|
||||||
|
fingerprint = key.fingerprint,
|
||||||
|
key_type = TryInto::<AspKeyType>::try_into(key.key_type).context("Unable to get key type from database")?,
|
||||||
|
alias = key.alias
|
||||||
|
};
|
||||||
|
|
||||||
|
let confirmation = Confirm::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Are you sure you want to delete this key?")
|
||||||
|
.interact_on(&Term::stderr())
|
||||||
|
.context("Unable to prompt on stderr")?;
|
||||||
|
|
||||||
|
if !confirmation {
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fingerprint = key.fingerprint.clone();
|
||||||
|
key.delete(&state.db).await.context("Unable to delete key")?;
|
||||||
|
println!("Successfully deleted key with fingerprint {}", fingerprint);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ pub mod generate;
|
||||||
#[cfg(feature = "gpg-compat")]
|
#[cfg(feature = "gpg-compat")]
|
||||||
pub mod import_gpg;
|
pub mod import_gpg;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
|
pub mod delete;
|
||||||
|
|
||||||
/// A subcommand to allow the management of keys, which can then be used to create, modify, or delete profiles.
|
/// A subcommand to allow the management of keys, which can then be used to create, modify, or delete profiles.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
@ -20,4 +21,5 @@ pub enum KeysSubcommands {
|
||||||
Export(export::KeysExportCommand),
|
Export(export::KeysExportCommand),
|
||||||
#[cfg(feature = "gpg-compat")]
|
#[cfg(feature = "gpg-compat")]
|
||||||
ImportGpg(import_gpg::KeysImportGpgCommand),
|
ImportGpg(import_gpg::KeysImportGpgCommand),
|
||||||
|
Delete(delete::KeysDeleteCommand),
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,12 @@ use crate::entities::keys::{Column as KeysColumn, Entity as KeysEntity, Model as
|
||||||
use crate::AspmState;
|
use crate::AspmState;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait AspmSubcommand: Parser {
|
pub trait AspmSubcommand: Parser + Sync {
|
||||||
async fn execute(&self, _state: AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(&self, _state: AspmState) -> Result<(), anyhow::Error> {
|
||||||
panic!("Not implemented")
|
panic!("Not implemented")
|
||||||
}
|
}
|
||||||
fn execute_sync(&self, _state: AspmState, _runtime: Runtime) -> Result<(), anyhow::Error> {
|
fn execute_sync(&self, state: AspmState, runtime: Runtime) -> Result<(), anyhow::Error> {
|
||||||
panic!("Not implemented")
|
runtime.block_on(async { self.execute(state).await })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -139,15 +139,10 @@ fn cli(parsed: AspmCommand) -> Result<(), anyhow::Error> {
|
||||||
// Call the subcommand
|
// Call the subcommand
|
||||||
match &parsed.subcommand {
|
match &parsed.subcommand {
|
||||||
AspmSubcommands::Keys(subcommand) => match &subcommand.subcommand {
|
AspmSubcommands::Keys(subcommand) => match &subcommand.subcommand {
|
||||||
KeysSubcommands::Generate(subcommand) => {
|
KeysSubcommands::Generate(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
runtime.block_on(async { subcommand.execute(state).await })
|
KeysSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
}
|
KeysSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
KeysSubcommands::List(subcommand) => {
|
KeysSubcommands::Delete(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
runtime.block_on(async { subcommand.execute(state).await })
|
|
||||||
}
|
|
||||||
KeysSubcommands::Export(subcommand) => {
|
|
||||||
runtime.block_on(async { subcommand.execute(state).await })
|
|
||||||
}
|
|
||||||
#[cfg(feature = "gpg-compat")]
|
#[cfg(feature = "gpg-compat")]
|
||||||
KeysSubcommands::ImportGpg(subcommand) => subcommand.execute_sync(state, runtime),
|
KeysSubcommands::ImportGpg(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue