coodev_runner/actions/
mod.rs1mod 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}