use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::{
Arc,
Mutex,
};
use hashbrown::HashMap;
use indicatif::{
MultiProgress,
ProgressDrawTarget,
};
use serde::Serialize;
use crate::cache::CacheStore;
use crate::defaults::{
default_ignore_errors,
default_shell,
default_verbose,
};
use super::{
ActiveTasks,
CompletedTasks,
ContainerRuntime,
Shell,
TaskRoot,
};
#[derive(Clone)]
pub struct TaskContext {
pub task_root: Arc<TaskRoot>,
pub active_tasks: ActiveTasks,
pub completed_tasks: CompletedTasks,
pub multi: Arc<MultiProgress>,
pub env_vars: HashMap<String, String>,
pub task_outputs: Arc<Mutex<HashMap<String, String>>>,
pub secret_vault_location: Option<String>,
pub secret_keys_location: Option<String>,
pub secret_key_name: Option<String>,
pub secret_gpg_key_id: Option<String>,
pub shell: Option<Arc<Shell>>,
pub container_runtime: Option<ContainerRuntime>,
pub ignore_errors: Option<bool>,
pub verbose: Option<bool>,
pub force: bool,
pub json_events: bool,
pub is_nested: bool,
pub cache_store: Arc<Mutex<CacheStore>>,
pub current_task_name: Option<String>,
}
impl TaskContext {
pub fn empty() -> Self {
let mp = MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
Self {
task_root: Arc::new(TaskRoot::default()),
active_tasks: Arc::new(Mutex::new(HashSet::new())),
completed_tasks: Arc::new(Mutex::new(HashSet::new())),
multi: Arc::new(mp),
env_vars: HashMap::new(),
task_outputs: Arc::new(Mutex::new(HashMap::new())),
secret_vault_location: None,
secret_keys_location: None,
secret_key_name: None,
secret_gpg_key_id: None,
shell: None,
container_runtime: None,
ignore_errors: None,
verbose: None,
force: false,
json_events: false,
is_nested: false,
cache_store: Arc::new(Mutex::new(CacheStore::default())),
current_task_name: None,
}
}
pub fn empty_with_root(task_root: Arc<TaskRoot>) -> Self {
let mp = MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
Self {
task_root: task_root.clone(),
active_tasks: Arc::new(Mutex::new(HashSet::new())),
completed_tasks: Arc::new(Mutex::new(HashSet::new())),
multi: Arc::new(mp),
env_vars: HashMap::new(),
task_outputs: Arc::new(Mutex::new(HashMap::new())),
secret_vault_location: None,
secret_keys_location: None,
secret_key_name: None,
secret_gpg_key_id: None,
shell: None,
container_runtime: None,
ignore_errors: None,
verbose: None,
force: false,
json_events: false,
is_nested: false,
cache_store: Arc::new(Mutex::new(CacheStore::default())),
current_task_name: None,
}
}
pub fn new(task_root: Arc<TaskRoot>) -> Self {
let cache_store = CacheStore::load_in_dir(&task_root.cache_base_dir()).unwrap_or_default();
Self {
task_root: task_root.clone(),
active_tasks: Arc::new(Mutex::new(HashSet::new())),
completed_tasks: Arc::new(Mutex::new(HashSet::new())),
multi: Arc::new(MultiProgress::new()),
env_vars: HashMap::new(),
task_outputs: Arc::new(Mutex::new(HashMap::new())),
secret_vault_location: task_root.vault_location.clone(),
secret_keys_location: task_root.keys_location.clone(),
secret_key_name: task_root.key_name.clone(),
secret_gpg_key_id: task_root.gpg_key_id.clone(),
shell: None,
container_runtime: task_root.container_runtime.clone(),
ignore_errors: None,
verbose: None,
force: false,
json_events: false,
is_nested: false,
cache_store: Arc::new(Mutex::new(cache_store)),
current_task_name: None,
}
}
pub fn new_with_options(task_root: Arc<TaskRoot>, force: bool, json_events: bool) -> Self {
let cache_store = CacheStore::load_in_dir(&task_root.cache_base_dir()).unwrap_or_default();
let multi = if json_events {
Arc::new(MultiProgress::with_draw_target(ProgressDrawTarget::hidden()))
} else {
Arc::new(MultiProgress::new())
};
Self {
task_root: task_root.clone(),
active_tasks: Arc::new(Mutex::new(HashSet::new())),
completed_tasks: Arc::new(Mutex::new(HashSet::new())),
multi,
env_vars: HashMap::new(),
task_outputs: Arc::new(Mutex::new(HashMap::new())),
secret_vault_location: task_root.vault_location.clone(),
secret_keys_location: task_root.keys_location.clone(),
secret_key_name: task_root.key_name.clone(),
secret_gpg_key_id: task_root.gpg_key_id.clone(),
shell: None,
container_runtime: task_root.container_runtime.clone(),
ignore_errors: None,
verbose: None,
force,
json_events,
is_nested: false,
cache_store: Arc::new(Mutex::new(cache_store)),
current_task_name: None,
}
}
pub fn from_context(context: &TaskContext) -> Self {
Self {
task_root: context.task_root.clone(),
active_tasks: context.active_tasks.clone(),
completed_tasks: context.completed_tasks.clone(),
multi: context.multi.clone(),
env_vars: context.env_vars.clone(),
task_outputs: Arc::new(Mutex::new(HashMap::new())),
secret_vault_location: context.secret_vault_location.clone(),
secret_keys_location: context.secret_keys_location.clone(),
secret_key_name: context.secret_key_name.clone(),
secret_gpg_key_id: context.secret_gpg_key_id.clone(),
shell: context.shell.clone(),
container_runtime: context.container_runtime.clone(),
ignore_errors: context.ignore_errors,
verbose: context.verbose,
force: context.force,
json_events: context.json_events,
is_nested: true,
cache_store: context.cache_store.clone(),
current_task_name: context.current_task_name.clone(),
}
}
pub fn from_context_with_args(context: &TaskContext, ignore_errors: bool, verbose: bool) -> Self {
Self {
task_root: context.task_root.clone(),
active_tasks: context.active_tasks.clone(),
completed_tasks: context.completed_tasks.clone(),
multi: context.multi.clone(),
env_vars: context.env_vars.clone(),
task_outputs: Arc::new(Mutex::new(HashMap::new())),
secret_vault_location: context.secret_vault_location.clone(),
secret_keys_location: context.secret_keys_location.clone(),
secret_key_name: context.secret_key_name.clone(),
secret_gpg_key_id: context.secret_gpg_key_id.clone(),
shell: context.shell.clone(),
container_runtime: context.container_runtime.clone(),
ignore_errors: Some(ignore_errors),
verbose: Some(verbose),
force: context.force,
json_events: context.json_events,
is_nested: true,
cache_store: context.cache_store.clone(),
current_task_name: context.current_task_name.clone(),
}
}
pub fn extend_env_vars<I>(&mut self, iter: I)
where
I: IntoIterator<Item = (String, String)>,
{
self.env_vars.extend(iter);
}
pub fn set_shell(&mut self, shell: &Shell) {
let shell = Arc::new(Shell::from_shell(shell));
self.shell = Some(shell);
}
pub fn set_secret_vault_location(&mut self, vault_location: impl Into<String>) {
self.secret_vault_location = Some(vault_location.into());
}
pub fn set_secret_keys_location(&mut self, keys_location: impl Into<String>) {
self.secret_keys_location = Some(keys_location.into());
}
pub fn set_secret_key_name(&mut self, key_name: impl Into<String>) {
self.secret_key_name = Some(key_name.into());
}
pub fn set_secret_gpg_key_id(&mut self, gpg_key_id: impl Into<String>) {
self.secret_gpg_key_id = Some(gpg_key_id.into());
}
pub fn set_container_runtime(&mut self, runtime: &ContainerRuntime) {
self.container_runtime = Some(runtime.clone());
}
pub fn set_ignore_errors(&mut self, ignore_errors: bool) {
self.ignore_errors = Some(ignore_errors);
}
pub fn set_verbose(&mut self, verbose: bool) {
self.verbose = Some(verbose);
}
pub fn insert_task_output(&self, name: impl Into<String>, value: impl Into<String>) -> anyhow::Result<()> {
let name = name.into();
let mut outputs = self
.task_outputs
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock task outputs - {}", e))?;
if outputs.contains_key(&name) {
anyhow::bail!("Task output already exists - {}", name);
}
outputs.insert(name, value.into());
Ok(())
}
pub fn get_task_output(&self, name: &str) -> anyhow::Result<Option<String>> {
let outputs = self
.task_outputs
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock task outputs - {}", e))?;
Ok(outputs.get(name).cloned())
}
pub fn has_task_output(&self, name: &str) -> anyhow::Result<bool> {
let outputs = self
.task_outputs
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock task outputs - {}", e))?;
Ok(outputs.contains_key(name))
}
pub fn shell(&self) -> Arc<Shell> {
self.shell.clone().unwrap_or_else(|| Arc::new(default_shell()))
}
pub fn ignore_errors(&self) -> bool {
self.ignore_errors.unwrap_or(default_ignore_errors())
}
pub fn verbose(&self) -> bool {
self.verbose.unwrap_or(default_verbose())
}
pub fn is_task_active(&self, task_name: &str) -> anyhow::Result<bool> {
let active = self
.active_tasks
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock active tasks - {}", e))?;
Ok(active.contains(task_name))
}
pub fn is_task_completed(&self, task_name: &str) -> anyhow::Result<bool> {
let completed = self
.completed_tasks
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock completed tasks - {}", e))?;
Ok(completed.contains(task_name))
}
pub fn mark_task_active(&self, task_name: &str) -> anyhow::Result<()> {
let mut active = self
.active_tasks
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock active tasks - {}", e))?;
active.insert(task_name.to_string());
Ok(())
}
pub fn unmark_task_active(&self, task_name: &str) -> anyhow::Result<()> {
let mut active = self
.active_tasks
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock active tasks - {}", e))?;
active.remove(task_name);
Ok(())
}
pub fn mark_task_complete(&self, task_name: &str) -> anyhow::Result<()> {
let mut completed = self
.completed_tasks
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock completed tasks - {}", e))?;
completed.insert(task_name.to_string());
Ok(())
}
pub fn emit_event<T: Serialize>(&self, value: &T) -> anyhow::Result<()> {
if self.json_events {
println!("{}", serde_json::to_string(value)?);
}
Ok(())
}
pub fn set_current_task_name(&mut self, task_name: &str) {
self.current_task_name = Some(task_name.to_string());
}
pub fn resolve_from_config(&self, value: &str) -> PathBuf {
self.task_root.resolve_from_config(value)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_task_context_1() -> anyhow::Result<()> {
{
let context = TaskContext::empty();
assert_eq!(context.shell().cmd(), "sh".to_string());
assert!(!context.ignore_errors());
assert!(context.verbose());
}
Ok(())
}
#[test]
fn test_task_context_2() -> anyhow::Result<()> {
{
let mut context = TaskContext::empty();
context.set_shell(&Shell::String("bash".to_string()));
assert_eq!(context.shell().cmd(), "bash".to_string());
}
Ok(())
}
#[test]
fn test_task_context_3() -> anyhow::Result<()> {
{
let mut context = TaskContext::empty();
context.extend_env_vars(vec![("key".to_string(), "value".to_string())]);
assert_eq!(context.env_vars.get("key"), Some(&"value".to_string()));
}
Ok(())
}
#[test]
fn test_task_context_4() -> anyhow::Result<()> {
{
let mut context = TaskContext::empty();
context.set_ignore_errors(true);
assert!(context.ignore_errors());
}
Ok(())
}
#[test]
fn test_task_context_5() -> anyhow::Result<()> {
{
let mut context = TaskContext::empty();
context.set_verbose(true);
assert!(context.verbose());
}
Ok(())
}
#[test]
fn test_task_context_outputs_are_stored() -> anyhow::Result<()> {
let context = TaskContext::empty();
context.insert_task_output("tag", "v1.0.0")?;
assert_eq!(context.get_task_output("tag")?, Some("v1.0.0".to_string()));
assert!(context.has_task_output("tag")?);
Ok(())
}
}