coodev_runner/actions/
mod.rs

1mod cache;
2mod git;
3
4use crate::user_config::{UserActionStep, UserStep};
5use cache::{CacheAction, RestoreCacheAction, SaveCacheAction};
6use git::GitCheckoutAction;
7use serde::{Deserialize, Serialize};
8use std::{collections::HashMap, fmt::Debug, sync::Arc};
9
10#[derive(Deserialize, Serialize, Debug, Clone)]
11pub struct ActionSteps {
12  pub pre: Option<UserStep>,
13  pub run: UserStep,
14  pub post: Option<UserStep>,
15}
16
17pub trait Action
18where
19  Self: std::fmt::Debug + std::marker::Sync + std::marker::Send + Debug,
20{
21  fn normalize(&self, step: UserActionStep) -> crate::Result<ActionSteps>;
22}
23
24#[derive(Debug, Clone)]
25pub struct Actions {
26  actions: HashMap<String, Arc<Box<dyn Action>>>,
27}
28
29impl Actions {
30  pub fn new() -> Self {
31    let mut actions: HashMap<String, Arc<Box<dyn Action>>> = HashMap::new();
32
33    actions.insert(
34      "cache/save".to_string(),
35      Arc::new(Box::new(SaveCacheAction::new())),
36    );
37
38    actions.insert(
39      "cache/restore".to_string(),
40      Arc::new(Box::new(RestoreCacheAction::new())),
41    );
42
43    actions.insert("cache".to_string(), Arc::new(Box::new(CacheAction::new())));
44
45    actions.insert(
46      "git/checkout".to_string(),
47      Arc::new(Box::new(GitCheckoutAction::new())),
48    );
49
50    Self { actions }
51  }
52
53  pub fn get(&self, name: &str) -> Option<Arc<Box<dyn Action>>> {
54    self.actions.get(name).cloned()
55  }
56
57  pub fn register(&mut self, name: String, action: Box<dyn Action>) {
58    self.actions.insert(name, Arc::new(action));
59  }
60}
61
62#[cfg(test)]
63mod tests {
64  use crate::{
65    actions::{Action, ActionSteps},
66    user_config::{UserActionStep, UserCommandStep, UserStep},
67    utils::test::{create_runner, create_workflow_options, create_workflow_runner},
68  };
69
70  #[test]
71  fn test_normalize_step_actions() -> anyhow::Result<()> {
72    let yaml = r#"
73jobs:
74  test-job:
75    name: Test Job
76    steps:
77      - name: Cache
78        uses: cache
79        with:
80          key: some-key-${COODEV_SHA}
81          path: .
82    "#;
83
84    let workflow_runner = create_workflow_runner(yaml)?;
85    let workflow = workflow_runner.get_workflow();
86
87    let steps = workflow.jobs.get("test-job").unwrap().steps.clone();
88    assert_eq!(steps.len(), 2);
89    assert_eq!(steps[0].name, Some("Cache".to_string()));
90    assert_eq!(steps[1].name, Some("Post Cache".to_string()));
91
92    Ok(())
93  }
94
95  #[test]
96  fn test_not_found_action() -> anyhow::Result<()> {
97    let yaml = r#"
98jobs:
99  test-job:
100    name: Test Job
101    steps:
102      - name: Not Found
103        uses: not-found-action
104    "#;
105
106    let res = create_workflow_runner(yaml);
107
108    if let Err(err) = res {
109      assert_eq!(
110        err.to_string(),
111        "Failed to parse user config: Action `not-found-action` is not found"
112      );
113    } else {
114      panic!("Should be error");
115    }
116
117    Ok(())
118  }
119
120  #[test]
121  fn test_custom_action() -> anyhow::Result<()> {
122    let yaml = r#"
123  jobs:
124    job:
125      steps:
126        - name: Custom Action
127          uses: custom-action
128      "#;
129
130    let runner = create_runner()?;
131
132    #[derive(Debug, Clone)]
133    struct CustomAction;
134
135    impl Action for CustomAction {
136      fn normalize(&self, _step: UserActionStep) -> crate::Result<ActionSteps> {
137        Ok(ActionSteps {
138          pre: None,
139          run: UserStep::Command(UserCommandStep {
140            name: Some("Custom Action".to_string()),
141            run: "echo 'Custom Action'".to_string(),
142            continue_on_error: None,
143            image: None,
144            environments: None,
145            secrets: None,
146            volumes: None,
147            timeout: None,
148            security_opts: None,
149          }),
150          post: None,
151        })
152      }
153    }
154
155    runner.register_action("custom-action", CustomAction);
156
157    let options = create_workflow_options(yaml);
158    let workflow_runner = runner.create_workflow_runner(options)?;
159    let workflow = workflow_runner.get_workflow();
160
161    let steps = workflow.jobs.get("job").unwrap().steps.clone();
162
163    assert_eq!(steps.len(), 1);
164    assert_eq!(steps[0].name, Some("Custom Action".to_string()));
165    assert_eq!(steps[0].run, "echo 'Custom Action'");
166
167    Ok(())
168  }
169}