WIP keyring

This commit is contained in:
Daniel Flanagan 2024-04-17 13:27:18 -05:00
parent 7dfcb2ad95
commit e581727d23
9 changed files with 989 additions and 53 deletions

931
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,7 @@ clap_derive = "4.5.4"
color-eyre = "0.6.3" color-eyre = "0.6.3"
crossterm = "0.27.0" crossterm = "0.27.0"
futures = "0.3.30" futures = "0.3.30"
keyring = "2.3.2"
ratatui = "0.26.2" ratatui = "0.26.2"
redact = "0.1.9" redact = "0.1.9"
regex = "1.10.3" regex = "1.10.3"

View file

@ -13,7 +13,7 @@ pub struct Cli {
#[command(subcommand)] #[command(subcommand)]
pub command: Commands, pub command: Commands,
/// The directory to write log files /// The directory to write log files (defaults to $XDG_CACHE_HOME/taskr/logs)
#[arg(long, default_value = None)] #[arg(long, default_value = None)]
pub logs_directory: Option<String>, pub logs_directory: Option<String>,
@ -22,6 +22,10 @@ pub struct Cli {
/// for details /// for details
#[arg(long, default_value = None)] #[arg(long, default_value = None)]
pub tracing_env_filter: Option<String>, pub tracing_env_filter: Option<String>,
/// The directory that data is stored in (defaults to $XDG_DATA_HOME/taskr/data)
#[arg(long, default_value = None)]
pub data_directory: Option<String>,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -63,8 +67,7 @@ mod list {
} }
impl Args { impl Args {
pub async fn list(&self) -> Result<()> { pub async fn list(&self, tasks: SharedTasks) -> Result<()> {
let tasks = Arc::new(crate::tasks::Tasks::try_new()?);
if self.sync { if self.sync {
eprintln!("Syncing..."); eprintln!("Syncing...");
tasks.sync().await?; tasks.sync().await?;
@ -92,9 +95,9 @@ mod sync {
pub struct Args {} pub struct Args {}
impl Args { impl Args {
pub async fn sync(&self) -> Result<()> { pub async fn sync(&self, tasks: SharedTasks) -> Result<()> {
eprintln!("Syncing..."); eprintln!("Syncing...");
Arc::new(crate::tasks::Tasks::try_new()?).sync().await?; tasks.sync().await?;
eprintln!("Done!"); eprintln!("Done!");
Ok(()) Ok(())
} }

View file

@ -6,9 +6,9 @@ pub enum Command {
} }
impl Command { impl Command {
pub async fn exec(&self) -> Result<()> { pub async fn exec(&self, tasks: SharedTasks) -> Result<()> {
match self { match self {
Command::Me(args) => args.me().await, Command::Me(args) => args.me(tasks).await,
} }
} }
} }
@ -17,8 +17,7 @@ impl Command {
pub struct MeArgs {} pub struct MeArgs {}
impl MeArgs { impl MeArgs {
pub async fn me(&self) -> Result<()> { pub async fn me(&self, tasks: SharedTasks) -> Result<()> {
let tasks = crate::tasks::Tasks::try_new()?;
println!("{:?}", tasks.gitlab.me().await?); println!("{:?}", tasks.gitlab.me().await?);
Ok(()) Ok(())
} }

View file

@ -6,9 +6,9 @@ pub enum Command {
} }
impl Command { impl Command {
pub async fn exec(&self) -> Result<()> { pub async fn exec(&self, tasks: SharedTasks) -> Result<()> {
match self { match self {
Command::Issue(args) => args.issue().await, Command::Issue(args) => args.issue(tasks).await,
} }
} }
} }
@ -20,8 +20,7 @@ pub struct IssueArgs {
} }
impl IssueArgs { impl IssueArgs {
pub async fn issue(&self) -> Result<()> { pub async fn issue(&self, tasks: SharedTasks) -> Result<()> {
let tasks = crate::tasks::Tasks::try_new()?;
let issue = tasks.jira.issue(&self.key).await?; let issue = tasks.jira.issue(&self.key).await?;
println!("{issue:?}"); println!("{issue:?}");
Ok(()) Ok(())

View file

@ -23,15 +23,15 @@ async fn main() -> Result<()> {
// https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/struct.WorkerGuard.html // https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/struct.WorkerGuard.html
let _log_guard = observe::setup_logging(cli.logs_directory, cli.tracing_env_filter)?; let _log_guard = observe::setup_logging(cli.logs_directory, cli.tracing_env_filter)?;
println!("Starting..."); println!("Initializing taskr...");
trace!("Starting..."); let tasks = Arc::new(crate::tasks::Tasks::try_new(cli.data_directory)?);
info!("Starting...");
let result = match cli.command { let result = match cli.command {
Commands::Sync(args) => args.sync().await, Commands::Sync(args) => args.sync(tasks).await,
Commands::Ui(_args) => tui::run().await, Commands::Ui(_args) => tui::run(tasks).await,
Commands::List(args) => args.list().await, Commands::List(args) => args.list(tasks).await,
Commands::Jira(jira) => jira.exec().await, Commands::Jira(jira) => jira.exec(tasks).await,
Commands::Gitlab(gitlab) => gitlab.exec().await, Commands::Gitlab(gitlab) => gitlab.exec(tasks).await,
}; };
result result
} }

View file

@ -5,3 +5,7 @@ pub use regex::Regex;
pub use std::sync::Arc; pub use std::sync::Arc;
pub use tokio::sync::Mutex; pub use tokio::sync::Mutex;
pub use tracing::{debug, error, info, trace, warn}; pub use tracing::{debug, error, info, trace, warn};
use crate::tasks::Tasks;
pub type SharedTasks = Arc<Tasks>;

View file

@ -4,6 +4,7 @@ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
env, env,
hash::RandomState, hash::RandomState,
path::Path,
process::Command, process::Command,
sync::OnceLock, sync::OnceLock,
}; };
@ -25,17 +26,41 @@ pub struct Tasks {
const MY_DONE_STATUS_CATEGORY_KEY: &str = "done"; const MY_DONE_STATUS_CATEGORY_KEY: &str = "done";
impl Tasks { impl Tasks {
pub fn try_new() -> Result<Self> { pub fn try_new<D>(data_dir: Option<D>) -> Result<Self>
// TODO: cache, use keyring, talk to a daemon, or otherwise cache this safely where
// TODO: or find a way to more-lazily load the token? D: AsRef<Path>,
let gl_token = env::var("GITLAB_TOKEN").or_else(|_| -> Result<String> { {
let output = Command::new("pass") // TODO: find a way to more-lazily load the token?
.arg("client/divvy/gitlab-glpat") let gitlab_token_entry = keyring::Entry::new("taskr", "gitlab_token")?;
.output()?;
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) let gl_token = match gitlab_token_entry.get_password() {
})?; Ok(token) => {
info!("GitLab token loaded from keyring");
token
}
Err(_) => {
let token = match env::var("GITLAB_TOKEN") {
Ok(token) => {
info!("GitLab token loaded from environment variable GITLAB_TOKEN");
token
}
Err(_) => {
let output = Command::new("pass")
.arg("client/divvy/gitlab-glpat")
.output()?;
let result = String::from_utf8_lossy(&output.stdout).trim().to_string();
info!("GitLab token loaded from password-store entry client/divvy/gitlab-glpat");
result
}
};
info!("GitLab token stored in keyring");
gitlab_token_entry.set_password(&token)?;
token
}
};
let gitlab = GitLab::try_new("https://git.hq.bill.com/api/v4", &gl_token)?; let gitlab = GitLab::try_new("https://git.hq.bill.com/api/v4", &gl_token)?;
// TODO: ensure the token works?
// TODO: cache, use keyring, talk to a daemon, or otherwise cache this safely // TODO: cache, use keyring, talk to a daemon, or otherwise cache this safely
// TODO: or find a way to more-lazily load the token? // TODO: or find a way to more-lazily load the token?
@ -48,7 +73,20 @@ impl Tasks {
})?; })?;
let jira = Jira::try_new("https://billcom.atlassian.net", &jira_token)?; let jira = Jira::try_new("https://billcom.atlassian.net", &jira_token)?;
let db = sled::open("data/tasks")?; let mut default_data_dir = Default::default();
let data_dir = data_dir
.as_ref()
.map(D::as_ref)
.map(Result::Ok)
.unwrap_or_else(|| {
// really want to avoid calling this so it's put in here to force it to be lazy
// also have to shove it in a function-scoped variable so the
// reference we create in this closure has the same lifetime as the function
default_data_dir =
xdg::BaseDirectories::new()?.create_data_directory("taskr/data")?;
Ok(default_data_dir.as_ref())
})?;
let db = sled::open(data_dir)?;
Ok(Self { gitlab, jira, db }) Ok(Self { gitlab, jira, db })
} }

View file

@ -1,6 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use crate::task; use crate::task;
use crate::tasks::Tasks;
use crossterm::{ use crossterm::{
event::{self, KeyCode, KeyEventKind}, event::{self, KeyCode, KeyEventKind},
@ -13,9 +12,7 @@ use ratatui::{
}; };
use std::io::stdout; use std::io::stdout;
pub async fn run() -> Result<()> { pub async fn run(t: SharedTasks) -> Result<()> {
let t = Tasks::try_new()?;
// print!("{ANSI_CLEAR}"); // print!("{ANSI_CLEAR}");
// let gitlab_user = tasks.gitlab.me().await?; // let gitlab_user = tasks.gitlab.me().await?;
// info!("{gitlab_user:#?}"); // info!("{gitlab_user:#?}");