flowey_cli/cli/debug/
interrogate.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::cli::FlowBackendCli;
5use flowey_core::node::FlowArch;
6use flowey_core::node::FlowBackend;
7use flowey_core::node::FlowPlatform;
8use flowey_core::node::GhOutput;
9use flowey_core::node::GhToRust;
10use flowey_core::node::NodeHandle;
11use flowey_core::node::RustToGh;
12use flowey_core::node::steps::rust::RustRuntimeServices;
13use flowey_core::node::user_facing::ClaimedGhParam;
14use flowey_core::node::user_facing::GhPermission;
15use flowey_core::node::user_facing::GhPermissionValue;
16use flowey_core::pipeline::HostExt;
17use flowey_core::pipeline::PipelineBackendHint;
18use std::collections::BTreeMap;
19
20/// (debug) get info about a specific node.
21///
22/// Information includes:
23/// - supported backends
24/// - supported requests
25/// - dependencies
26/// - inline steps (with exec-snippet indices)*
27///
28/// *inline-steps will only be listed if they are enabled via the particular
29/// combination of specified {backend x requests}. For a complete picture
30/// possible dependencies and available steps, you must inspect the Node's
31/// code and documentation directly.
32#[derive(clap::Args)]
33pub struct Interrogate {
34    /// Node to interrogate
35    node_handle: String,
36
37    /// Flow backend to interrogate with
38    flow_backend: FlowBackendCli,
39
40    /// Apply a request onto the node (as JSON)
41    #[clap(long)]
42    req: Vec<String>,
43}
44
45impl Interrogate {
46    pub fn run(self) -> anyhow::Result<()> {
47        let Self {
48            node_handle,
49            flow_backend,
50            req,
51        } = self;
52
53        let raw_json_reqs: Vec<Box<[u8]>> = req
54            .into_iter()
55            .map(|v| v.as_bytes().to_vec().into())
56            .collect();
57
58        let Some(node_handle) = NodeHandle::try_from_modpath(&node_handle) else {
59            anyhow::bail!("could not find node with that name");
60        };
61
62        let mut node = node_handle.new_erased_node();
63
64        let mut dep_registration_backend = InterrogateDepRegistrationBackend;
65        let mut dep_registration = flowey_core::node::new_import_ctx(&mut dep_registration_backend);
66
67        let mut ctx_backend = InterrogateCtx::new(flow_backend.into(), node_handle);
68
69        println!(
70            "# interrogating with {}",
71            match flow_backend {
72                FlowBackendCli::Ado => "ado",
73                FlowBackendCli::Local => "local",
74                FlowBackendCli::Github => "github",
75            }
76        );
77
78        node.imports(&mut dep_registration);
79
80        let mut ctx = flowey_core::node::new_node_ctx(&mut ctx_backend);
81        node.emit(raw_json_reqs.clone(), &mut ctx)?;
82
83        Ok(())
84    }
85}
86
87struct InterrogateDepRegistrationBackend;
88
89impl flowey_core::node::ImportCtxBackend for InterrogateDepRegistrationBackend {
90    fn on_possible_dep(&mut self, node_handle: NodeHandle) {
91        println!("[dep?] {}", node_handle.modpath())
92    }
93}
94
95struct InterrogateCtx {
96    flow_backend: FlowBackend,
97    current_node: NodeHandle,
98    idx_tracker: usize,
99    var_tracker: usize,
100}
101
102impl InterrogateCtx {
103    fn new(flow_backend: FlowBackend, current_node: NodeHandle) -> Self {
104        Self {
105            flow_backend,
106            current_node,
107            idx_tracker: 0,
108            var_tracker: 0,
109        }
110    }
111}
112
113impl flowey_core::node::NodeCtxBackend for InterrogateCtx {
114    fn on_emit_rust_step(
115        &mut self,
116        label: &str,
117        _can_merge: bool,
118        _code: Box<
119            dyn for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
120        >,
121    ) {
122        println!("[step][rust][{}] # {}", self.idx_tracker, label);
123        self.idx_tracker += 1;
124    }
125
126    fn on_emit_ado_step(
127        &mut self,
128        label: &str,
129        yaml_snippet: Box<
130            dyn for<'a> FnOnce(
131                &'a mut flowey_core::node::user_facing::AdoStepServices<'_>,
132            ) -> String,
133        >,
134        code: Option<
135            Box<
136                dyn for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
137            >,
138        >,
139        _condvar: Option<String>,
140    ) {
141        println!(
142            "[step][yaml]    # {}{}",
143            if code.is_some() {
144                "(+inline script) "
145            } else {
146                ""
147            },
148            label
149        );
150        let mut fresh_ado_var = || "<dummy>".into();
151        let mut access = flowey_core::node::steps::ado::new_ado_step_services(&mut fresh_ado_var);
152        let raw_snippet = yaml_snippet(&mut access);
153
154        let snippet: Result<serde_yaml::Value, _> = serde_yaml::from_str(&raw_snippet);
155        match snippet {
156            Ok(snippet) => print!("{}", serde_yaml::to_string(&snippet).unwrap()),
157            Err(e) => {
158                log::error!("invalid snippet: {}", e);
159                println!(">>>");
160                println!("{}", raw_snippet);
161                println!("<<<");
162            }
163        };
164
165        self.idx_tracker += 1;
166    }
167
168    fn on_emit_gh_step(
169        &mut self,
170
171        label: &str,
172        _uses: &str,
173        _with: BTreeMap<String, ClaimedGhParam>,
174        _condvar: Option<String>,
175        _outputs: BTreeMap<String, Vec<GhOutput>>,
176        _permissions: BTreeMap<GhPermission, GhPermissionValue>,
177        _gh_to_rust: Vec<GhToRust>,
178        _rust_to_gh: Vec<RustToGh>,
179    ) {
180        println!("[step][yaml]    # {}", label);
181        self.idx_tracker += 1;
182    }
183
184    fn on_emit_side_effect_step(&mut self) {
185        println!("[step][anchor]");
186    }
187
188    fn backend(&mut self) -> FlowBackend {
189        self.flow_backend
190    }
191
192    fn platform(&mut self) -> FlowPlatform {
193        FlowPlatform::host(PipelineBackendHint::Local)
194    }
195
196    fn arch(&mut self) -> FlowArch {
197        // xtask-fmt allow-target-arch oneoff-flowey
198        if cfg!(target_arch = "x86_64") {
199            FlowArch::X86_64
200        // xtask-fmt allow-target-arch oneoff-flowey
201        } else if cfg!(target_arch = "aarch64") {
202            FlowArch::Aarch64
203        } else {
204            unreachable!("flowey only runs on X86_64 or Aarch64 at the moment")
205        }
206    }
207
208    fn on_request(&mut self, node_handle: NodeHandle, req: anyhow::Result<Box<[u8]>>) {
209        match req {
210            Ok(data) => {
211                let data = match String::from_utf8(data.into()) {
212                    Ok(data) => data,
213                    Err(e) => e
214                        .into_bytes()
215                        .iter()
216                        .map(|b| format!("(raw) {:02x}", b))
217                        .collect::<Vec<_>>()
218                        .join(""),
219                };
220                println!("[req] {} <-- {}", node_handle.modpath(), data)
221            }
222            Err(e) => {
223                log::error!("error serializing inter-node request: {:#}", e)
224            }
225        }
226    }
227
228    fn on_new_var(&mut self) -> String {
229        let v = self.var_tracker;
230        self.var_tracker += 1;
231        format!("<dummy>:{}", v)
232    }
233
234    fn on_claimed_runtime_var(&mut self, var: &str, is_read: bool) {
235        println!(
236            "[var][claim] {} {}",
237            var,
238            if is_read { "(read)" } else { "(write)" },
239        )
240    }
241
242    fn current_node(&self) -> NodeHandle {
243        self.current_node
244    }
245
246    fn persistent_dir_path_var(&mut self) -> Option<String> {
247        Some("<dummy>".into())
248    }
249
250    fn on_unused_read_var(&mut self, _var: &str) {
251        // not relevant
252    }
253}