mod cache;
mod git;
use crate::user_config::{UserActionStep, UserStep};
use cache::{CacheAction, RestoreCacheAction, SaveCacheAction};
use git::GitCheckoutAction;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt::Debug, sync::Arc};
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ActionSteps {
pub pre: Option<UserStep>,
pub run: UserStep,
pub post: Option<UserStep>,
}
pub trait Action
where
Self: std::fmt::Debug + std::marker::Sync + std::marker::Send + Debug,
{
fn normalize(&self, step: UserActionStep) -> crate::Result<ActionSteps>;
}
#[derive(Debug, Clone)]
pub struct Actions {
actions: HashMap<String, Arc<Box<dyn Action>>>,
}
impl Actions {
pub fn new() -> Self {
let mut actions: HashMap<String, Arc<Box<dyn Action>>> = HashMap::new();
actions.insert(
"cache/save".to_string(),
Arc::new(Box::new(SaveCacheAction::new())),
);
actions.insert(
"cache/restore".to_string(),
Arc::new(Box::new(RestoreCacheAction::new())),
);
actions.insert("cache".to_string(), Arc::new(Box::new(CacheAction::new())));
actions.insert(
"git/checkout".to_string(),
Arc::new(Box::new(GitCheckoutAction::new())),
);
Self { actions }
}
pub fn get(&self, name: &str) -> Option<Arc<Box<dyn Action>>> {
self.actions.get(name).cloned()
}
pub fn register(&mut self, name: String, action: Box<dyn Action>) {
self.actions.insert(name, Arc::new(action));
}
}
#[cfg(test)]
mod tests {
use crate::{
actions::{Action, ActionSteps},
user_config::{UserActionStep, UserCommandStep, UserStep},
utils::test::{create_runner, create_workflow_options, create_workflow_runner},
};
#[test]
fn test_normalize_step_actions() -> anyhow::Result<()> {
let yaml = r#"
jobs:
test-job:
name: Test Job
steps:
- name: Cache
uses: cache
with:
key: some-key-${COODEV_SHA}
path: .
"#;
let workflow_runner = create_workflow_runner(yaml)?;
let workflow = workflow_runner.get_workflow();
let steps = workflow.jobs.get("test-job").unwrap().steps.clone();
assert_eq!(steps.len(), 2);
assert_eq!(steps[0].name, Some("Cache".to_string()));
assert_eq!(steps[1].name, Some("Post Cache".to_string()));
Ok(())
}
#[test]
fn test_not_found_action() -> anyhow::Result<()> {
let yaml = r#"
jobs:
test-job:
name: Test Job
steps:
- name: Not Found
uses: not-found-action
"#;
let res = create_workflow_runner(yaml);
if let Err(err) = res {
assert_eq!(
err.to_string(),
"Failed to parse user config: Action `not-found-action` is not found"
);
} else {
panic!("Should be error");
}
Ok(())
}
#[test]
fn test_custom_action() -> anyhow::Result<()> {
let yaml = r#"
jobs:
job:
steps:
- name: Custom Action
uses: custom-action
"#;
let runner = create_runner()?;
#[derive(Debug, Clone)]
struct CustomAction;
impl Action for CustomAction {
fn normalize(&self, _step: UserActionStep) -> crate::Result<ActionSteps> {
Ok(ActionSteps {
pre: None,
run: UserStep::Command(UserCommandStep {
name: Some("Custom Action".to_string()),
run: "echo 'Custom Action'".to_string(),
continue_on_error: None,
image: None,
environments: None,
secrets: None,
volumes: None,
timeout: None,
security_opts: None,
}),
post: None,
})
}
}
runner.register_action("custom-action", CustomAction);
let options = create_workflow_options(yaml);
let workflow_runner = runner.create_workflow_runner(options)?;
let workflow = workflow_runner.get_workflow();
let steps = workflow.jobs.get("job").unwrap().steps.clone();
assert_eq!(steps.len(), 1);
assert_eq!(steps[0].name, Some("Custom Action".to_string()));
assert_eq!(steps[0].run, "echo 'Custom Action'");
Ok(())
}
}