ambient_ci/action_impl/
custom.rs1use std::{collections::HashMap, path::Path, process::Command};
2
3use clingwrap::runner::{CommandError, CommandRunner};
4use serde::{Deserialize, Serialize};
5
6use crate::{
7 action::{ActionError, Context},
8 action_impl::ActionImpl,
9 runlog::RunLogSource,
10};
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct Custom {
24 pub name: String,
26
27 #[serde(default)]
29 pub args: HashMap<String, serde_norway::Value>,
30}
31
32impl Custom {
33 pub fn new(name: String, args: HashMap<String, serde_norway::Value>) -> Self {
35 Self { name, args }
36 }
37}
38
39impl ActionImpl for Custom {
40 fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
41 let source = context.source_dir().to_path_buf();
42 let exe = Path::new(".ambient").join(&self.name);
43 let exe_exists = exe.exists();
44 let json = serde_json::to_string(&self.args).map_err(CustomError::ArgsToJson)?;
45
46 context.runlog().custom_action_starts(
47 RunLogSource::Plan,
48 source.clone(),
49 self.clone(),
50 exe.clone(),
51 exe_exists,
52 );
53
54 let mut cmd = Command::new(exe);
55 cmd.current_dir(&source);
56 cmd.envs(context.env());
57 for (key, value) in self.args.iter() {
58 let key = format!("AMBIENT_CI_{key}");
59 let value = serde_json::to_string(value).map_err(CustomError::ArgsToJson)?;
60 cmd.env(key, value);
61 }
62 let mut runner = CommandRunner::new(cmd);
63 runner.feed_stdin(json.as_bytes());
64 runner.capture_stdout();
65 runner.capture_stderr();
66
67 let result = runner.execute();
68 if let Ok(output) = result {
69 println!("{:?}", String::from_utf8_lossy(&output.stdout));
72 context
73 .runlog()
74 .custom_action_output(RunLogSource::Plan, output.stdout, output.stderr);
75 } else if let Err(err) = result {
76 if let CommandError::CommandFailed { output, .. } = &err {
77 context.runlog().custom_action_output(
78 RunLogSource::Plan,
79 output.stdout.clone(),
80 output.stderr.clone(),
81 );
82 }
83 Err(CustomError::Custom(self.name.to_string(), err))?;
84 }
85
86 Ok(())
87 }
88}
89
90#[derive(Debug, thiserror::Error)]
92pub enum CustomError {
93 #[error("failed to run custom action {0:?}")]
95 Custom(String, #[source] clingwrap::runner::CommandError),
96
97 #[error("failed to convert custom action into JSON")]
99 ArgsToJson(#[source] serde_json::Error),
100}
101
102impl From<CustomError> for ActionError {
103 fn from(value: CustomError) -> Self {
104 Self::Custom(value)
105 }
106}