// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. use crate::cli::FlowBackendCli; use flowey_core::node::FlowArch; use flowey_core::node::FlowBackend; use flowey_core::node::FlowPlatform; use flowey_core::node::GhOutput; use flowey_core::node::GhToRust; use flowey_core::node::NodeHandle; use flowey_core::node::RustToGh; use flowey_core::node::steps::rust::RustRuntimeServices; use flowey_core::node::user_facing::ClaimedGhParam; use flowey_core::node::user_facing::GhPermission; use flowey_core::node::user_facing::GhPermissionValue; use flowey_core::pipeline::HostExt; use flowey_core::pipeline::PipelineBackendHint; use std::collections::BTreeMap; /// (debug) get info about a specific node. /// /// Information includes: /// - supported backends /// - supported requests /// - dependencies /// - inline steps (with exec-snippet indices)* /// /// *inline-steps will only be listed if they are enabled via the particular /// combination of specified {backend x requests}. For a complete picture /// possible dependencies and available steps, you must inspect the Node's /// code and documentation directly. #[derive(clap::Args)] pub struct Interrogate { /// Node to interrogate node_handle: String, /// Flow backend to interrogate with flow_backend: FlowBackendCli, /// Apply a request onto the node (as JSON) #[clap(long)] req: Vec, /// Apply a config onto the node (as JSON) #[clap(long)] config: Vec, } impl Interrogate { pub fn run(self) -> anyhow::Result<()> { let Self { node_handle, flow_backend, req, config, } = self; let raw_json_reqs: Vec> = req .into_iter() .map(|v| v.as_bytes().to_vec().into()) .collect(); let raw_json_configs: Vec> = config .into_iter() .map(|v| v.as_bytes().to_vec().into()) .collect(); let Some(node_handle) = NodeHandle::try_from_modpath(&node_handle) else { anyhow::bail!("could not find node with that name"); }; let mut node = node_handle.new_erased_node(); let mut dep_registration_backend = InterrogateDepRegistrationBackend; let mut dep_registration = flowey_core::node::new_import_ctx(&mut dep_registration_backend); let mut ctx_backend = InterrogateCtx::new(flow_backend.into(), node_handle); println!( "# interrogating with {}", match flow_backend { FlowBackendCli::Ado => "ado", FlowBackendCli::Local => "local", FlowBackendCli::Github => "github", } ); node.imports(&mut dep_registration); let mut ctx = flowey_core::node::new_node_ctx(&mut ctx_backend); node.emit(raw_json_configs, raw_json_reqs.clone(), &mut ctx)?; Ok(()) } } struct InterrogateDepRegistrationBackend; impl flowey_core::node::ImportCtxBackend for InterrogateDepRegistrationBackend { fn on_possible_dep(&mut self, node_handle: NodeHandle) { println!("[dep?] {}", node_handle.modpath()) } } struct InterrogateCtx { flow_backend: FlowBackend, current_node: NodeHandle, idx_tracker: usize, var_tracker: usize, } impl InterrogateCtx { fn new(flow_backend: FlowBackend, current_node: NodeHandle) -> Self { Self { flow_backend, current_node, idx_tracker: 0, var_tracker: 0, } } } impl flowey_core::node::NodeCtxBackend for InterrogateCtx { fn on_emit_rust_step( &mut self, label: &str, _can_merge: bool, _code: Box< dyn for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static, >, ) { println!("[step][rust][{}] # {}", self.idx_tracker, label); self.idx_tracker += 1; } fn on_emit_ado_step( &mut self, label: &str, yaml_snippet: Box< dyn for<'a> FnOnce( &'a mut flowey_core::node::user_facing::AdoStepServices<'_>, ) -> String, >, code: Option< Box< dyn for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static, >, >, _condvar: Option, ) { println!( "[step][yaml] # {}{}", if code.is_some() { "(+inline script) " } else { "" }, label ); let mut fresh_ado_var = || "".into(); let mut access = flowey_core::node::steps::ado::new_ado_step_services(&mut fresh_ado_var); let raw_snippet = yaml_snippet(&mut access); let snippet: Result = serde_yaml::from_str(&raw_snippet); match snippet { Ok(snippet) => print!("{}", serde_yaml::to_string(&snippet).unwrap()), Err(e) => { log::error!("invalid snippet: {}", e); println!(">>>"); println!("{}", raw_snippet); println!("<<<"); } }; self.idx_tracker += 1; } fn on_emit_gh_step( &mut self, label: &str, _uses: &str, _with: BTreeMap, _condvar: Option, _outputs: BTreeMap>, _permissions: BTreeMap, _gh_to_rust: Vec, _rust_to_gh: Vec, ) { println!("[step][yaml] # {}", label); self.idx_tracker += 1; } fn on_emit_side_effect_step(&mut self) { println!("[step][anchor]"); } fn backend(&mut self) -> FlowBackend { self.flow_backend } fn platform(&mut self) -> FlowPlatform { FlowPlatform::host(PipelineBackendHint::Local) } fn arch(&mut self) -> FlowArch { // xtask-fmt allow-target-arch oneoff-flowey if cfg!(target_arch = "x86_64") { FlowArch::X86_64 // xtask-fmt allow-target-arch oneoff-flowey } else if cfg!(target_arch = "aarch64") { FlowArch::Aarch64 } else { unreachable!("flowey only runs on X86_64 or Aarch64 at the moment") } } fn on_request(&mut self, node_handle: NodeHandle, req: anyhow::Result>) { match req { Ok(data) => { let data = match String::from_utf8(data.into()) { Ok(data) => data, Err(e) => e .into_bytes() .iter() .map(|b| format!("(raw) {:02x}", b)) .collect::>() .join(""), }; println!("[req] {} <-- {}", node_handle.modpath(), data) } Err(e) => { log::error!("error serializing inter-node request: {:#}", e) } } } fn on_config(&mut self, node_handle: NodeHandle, config: anyhow::Result>) { match config { Ok(data) => { let data = match String::from_utf8(data.into()) { Ok(data) => data, Err(e) => e .into_bytes() .iter() .map(|b| format!("(raw) {:02x}", b)) .collect::>() .join(""), }; println!("[config] {} <-- {}", node_handle.modpath(), data) } Err(e) => { log::error!("error serializing inter-node config: {:#}", e) } } } fn on_new_var(&mut self) -> String { let v = self.var_tracker; self.var_tracker += 1; format!(":{}", v) } fn on_claimed_runtime_var(&mut self, var: &str, is_read: bool) { println!( "[var][claim] {} {}", var, if is_read { "(read)" } else { "(write)" }, ) } fn current_node(&self) -> NodeHandle { self.current_node } fn persistent_dir_path_var(&mut self) -> Option { Some("".into()) } fn on_unused_read_var(&mut self, _var: &str) { // not relevant } }