byteor_actions/
resolver.rs1use byteor_core::config::Environment;
4use byteor_pipeline_exec::{ResolvedStage, StageResolver};
5
6pub const STAGE_KEY_HTTP_POST_PREFIX: &str = "http_post:";
8
9pub const STAGE_KEY_EXEC_PREFIX: &str = "exec:";
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct ActionStageFamily {
15 pub key_prefix: &'static str,
17 pub description: &'static str,
19 pub approval_capabilities: &'static [&'static str],
21}
22
23const ACTION_STAGE_FAMILIES: &[ActionStageFamily] = &[
24 ActionStageFamily {
25 key_prefix: STAGE_KEY_HTTP_POST_PREFIX,
26 description: "HTTP POST side-effect stage family",
27 approval_capabilities: &["execute_side_effects", "http_post"],
28 },
29 ActionStageFamily {
30 key_prefix: STAGE_KEY_EXEC_PREFIX,
31 description: "Process execution side-effect stage family",
32 approval_capabilities: &["execute_side_effects", "exec"],
33 },
34];
35
36pub fn stage_families() -> &'static [ActionStageFamily] {
38 ACTION_STAGE_FAMILIES
39}
40
41pub struct ActionStages {
49 policy: byteor_policy::Policy,
50 is_replay: bool,
51 dry_run: bool,
52 approval_token_present: bool,
53 replay: byteor_policy::ReplayAccounting,
54}
55
56impl ActionStages {
57 pub fn new(
59 policy: byteor_policy::Policy,
60 is_replay: bool,
61 dry_run: bool,
62 approval_token_present: bool,
63 replay: byteor_policy::ReplayAccounting,
64 ) -> Self {
65 Self {
66 policy,
67 is_replay,
68 dry_run,
69 approval_token_present,
70 replay,
71 }
72 }
73
74 fn decide(&self, name: &str) -> byteor_policy::Decision {
75 let req = byteor_policy::ActionRequest {
76 env: self.policy.env,
77 is_replay: self.is_replay,
78 dry_run: self.dry_run,
79 action: byteor_policy::ActionDecl {
80 name,
81 capability: byteor_policy::ActionCapability::SideEffecting,
82 },
83 approval_token_present: self.approval_token_present,
84 replay: self.replay,
85 };
86 byteor_policy::decide(&self.policy, &req)
87 }
88
89 pub fn safe_defaults(env: Environment) -> byteor_policy::Policy {
91 byteor_policy::Policy::safe_defaults(env)
92 }
93}
94
95impl StageResolver for ActionStages {
96 fn resolve(&self, stage: &str) -> Option<ResolvedStage> {
97 if let Some(url) = crate::http_post::parse_http_post_url(stage) {
98 let decision = self.decide("http_post");
99 let st = crate::http_post::HttpPostStage::new(url.to_string(), decision).ok()?;
100 return Some(ResolvedStage::Stateful(Box::new(st)));
101 }
102
103 if let Some(cmd) = crate::exec::parse_exec_cmd(stage) {
104 let decision = self.decide("exec");
105 let st = crate::exec::ExecStage::new(
106 cmd.program.to_string(),
107 cmd.args.into_iter().map(|s| s.to_string()).collect(),
108 decision,
109 )
110 .ok()?;
111 return Some(ResolvedStage::Stateful(Box::new(st)));
112 }
113
114 None
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn stage_families_publish_parameterized_keys() {
124 let families = stage_families();
125 assert_eq!(families.len(), 2);
126 assert_eq!(families[0].key_prefix, STAGE_KEY_HTTP_POST_PREFIX);
127 assert_eq!(families[1].key_prefix, STAGE_KEY_EXEC_PREFIX);
128 assert!(families[0].approval_capabilities.contains(&"http_post"));
129 assert!(families[1].approval_capabilities.contains(&"exec"));
130 }
131}