1use crate::gcloud::GcloudError;
2
3#[allow(async_fn_in_trait)]
7pub trait GcloudExecutor: Send + Sync {
8 async fn exec(&self, args: &[String]) -> Result<String, GcloudError>;
10
11 async fn exec_streaming(&self, args: &[String]) -> Result<(), GcloudError>;
13
14 async fn exec_with_stdin(
16 &self,
17 args: &[String],
18 stdin_data: &[u8],
19 ) -> Result<String, GcloudError>;
20}
21
22pub struct RealExecutor;
24
25impl GcloudExecutor for RealExecutor {
26 async fn exec(&self, args: &[String]) -> Result<String, GcloudError> {
27 use std::process::Stdio;
28
29 let output = tokio::process::Command::new("gcloud")
30 .args(args)
31 .stdout(Stdio::piped())
32 .stderr(Stdio::piped())
33 .output()
34 .await
35 .map_err(|e| GcloudError::NotFound { source: e })?;
36
37 if output.status.success() {
38 String::from_utf8(output.stdout).map_err(|e| GcloudError::InvalidUtf8 { source: e })
39 } else {
40 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
41 Err(GcloudError::CommandFailed {
42 args: args.to_vec(),
43 stderr,
44 })
45 }
46 }
47
48 async fn exec_streaming(&self, args: &[String]) -> Result<(), GcloudError> {
49 use std::process::Stdio;
50
51 let status = tokio::process::Command::new("gcloud")
52 .args(args)
53 .stdout(Stdio::inherit())
54 .stderr(Stdio::inherit())
55 .status()
56 .await
57 .map_err(|e| GcloudError::NotFound { source: e })?;
58
59 if status.success() {
60 Ok(())
61 } else {
62 Err(GcloudError::CommandFailed {
63 args: args.to_vec(),
64 stderr: format!("exit code: {status}"),
65 })
66 }
67 }
68
69 async fn exec_with_stdin(
70 &self,
71 args: &[String],
72 stdin_data: &[u8],
73 ) -> Result<String, GcloudError> {
74 use std::process::Stdio;
75 use tokio::io::AsyncWriteExt;
76
77 let mut child = tokio::process::Command::new("gcloud")
78 .args(args)
79 .stdin(Stdio::piped())
80 .stdout(Stdio::piped())
81 .stderr(Stdio::piped())
82 .spawn()
83 .map_err(|e| GcloudError::NotFound { source: e })?;
84
85 if let Some(mut stdin) = child.stdin.take() {
86 stdin
87 .write_all(stdin_data)
88 .await
89 .map_err(|e| GcloudError::StdinWrite { source: e })?;
90 stdin
91 .shutdown()
92 .await
93 .map_err(|e| GcloudError::StdinWrite { source: e })?;
94 }
95
96 let output = child
97 .wait_with_output()
98 .await
99 .map_err(|e| GcloudError::NotFound { source: e })?;
100
101 if output.status.success() {
102 String::from_utf8(output.stdout).map_err(|e| GcloudError::InvalidUtf8 { source: e })
103 } else {
104 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
105 Err(GcloudError::CommandFailed {
106 args: args.to_vec(),
107 stderr,
108 })
109 }
110 }
111}