From ec9ce8fac3f4232116d4931db3441be37461d5c6 Mon Sep 17 00:00:00 2001 From: marshmallow Date: Fri, 13 Mar 2026 10:04:55 +1100 Subject: [PATCH 1/2] add --dry-run arg --- crates/cli/src/apply.rs | 110 ++++++++++++++++++++++++++++++++-------- crates/cli/src/cli.rs | 4 ++ 2 files changed, 92 insertions(+), 22 deletions(-) diff --git a/crates/cli/src/apply.rs b/crates/cli/src/apply.rs index 850c278..3397508 100644 --- a/crates/cli/src/apply.rs +++ b/crates/cli/src/apply.rs @@ -52,10 +52,7 @@ fn read_apply_targets_from_stdin() -> Result<(Vec, Vec)> { })) } -fn resolve_targets( - on: &[ApplyTarget], - modifiers: &mut SubCommandModifiers, -) -> Result<(HashSet, HashSet)> { +fn resolve_targets(on: &[ApplyTarget]) -> Result<(HashSet, HashSet)> { on.iter() .try_fold((HashSet::new(), HashSet::new()), |result, target| { let (mut tags, mut names) = result; @@ -67,8 +64,8 @@ fn resolve_targets( names.insert(name.clone()); } ApplyTarget::Stdin => { - modifiers.non_interactive = true; let (found_tags, found_names) = read_apply_targets_from_stdin()?; + names.extend(found_names); tags.extend(found_tags); } @@ -100,14 +97,20 @@ pub async fn apply( args: CommonVerbArgs, partition: Partitions, make_goal: F, - mut modifiers: SubCommandModifiers, + modifiers: SubCommandModifiers, ) -> Result<()> where - F: Fn(&Name, &Node) -> Goal, + F: Fn(&Name, &Node) -> Goal + Clone, { let location = Arc::new(location); - let (tags, names) = resolve_targets(&args.on, &mut modifiers)?; + // stdin implies non_interactive + let mut modifiers = modifiers; + if args.on.iter().any(|t| matches!(t, ApplyTarget::Stdin)) { + modifiers.non_interactive = true; + } + + let (tags, names) = resolve_targets(&args.on)?; let selected_names: Vec<_> = hive .nodes @@ -121,28 +124,93 @@ where .map(|(name, _)| name.clone()) .collect(); - let num_selected = selected_names.len(); - let partitioned_names = partition_arr(selected_names, &partition); - if num_selected != partitioned_names.len() { - info!( - "Partitioning reduced selected number of nodes from {num_selected} to {}", - partitioned_names.len() - ); - } - STATUS .lock() .add_many(&partitioned_names.iter().collect::>()); + if args.dry_run { + dry_activate_nodes( + hive, + &partitioned_names, + make_goal, + &location, + &should_quit, + modifiers, + ); + + return Ok(()); + } + + apply_nodes( + hive, + &partitioned_names, + make_goal, + location, + should_quit, + args.parallel, + modifiers, + ) + .await +} + +fn dry_activate_nodes( + hive: &Hive, + names: &[Name], + make_goal: F, + location: &Arc, + should_quit: &Arc, + modifiers: SubCommandModifiers, +) where + F: Fn(&Name, &Node) -> Goal, +{ + for name in names { + let node = hive.nodes.get(name).unwrap(); + + let goal = make_goal(name, node); + let plan = plan_for_node( + node, + name.clone(), + &goal, + location.clone(), + &modifiers, + should_quit.clone(), + ); + + let goal_str = match &goal { + Goal::Build => "Build".to_string(), + Goal::Apply(args) => format!("Apply {:?}", args.goal), + }; + + println!("Node: {name}"); + println!("Goal: {goal_str}"); + println!("Steps:"); + for step in &plan.steps { + println!(" - {step}"); + } + println!(); + } +} + +async fn apply_nodes( + hive: &mut Hive, + names: &[Name], + make_goal: F, + location: Arc, + should_quit: Arc, + parallel: usize, + modifiers: SubCommandModifiers, +) -> Result<()> +where + F: Fn(&Name, &Node) -> Goal, +{ let mut set = hive .nodes .iter_mut() - .filter(|(name, _)| partitioned_names.contains(name)) + .filter(|(name, _)| names.contains(name)) .map(|(name, node)| { let goal = make_goal(name, node); - let plan = plan_for_node( node, name.clone(), @@ -159,7 +227,7 @@ where error!("There are no nodes selected for deployment"); } - let futures = futures::stream::iter(set).buffer_unordered(args.parallel); + let futures = futures::stream::iter(set).buffer_unordered(parallel); let result = futures.collect::>().await; let (successful, errors): (Vec<_>, Vec<_>) = @@ -179,9 +247,7 @@ where } if !errors.is_empty() { - // clear the status bar if we are about to print error messages STATUS.lock().clear(&mut stderr()); - return Err(NodeErrors( errors .into_iter() diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index 5e558ee..6afb46e 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -173,6 +173,10 @@ pub struct CommonVerbArgs { #[arg(short, long, default_value_t = 10, value_parser=more_than_zero)] pub parallel: usize, + + /// Print the plan for each node without executing it. + #[arg(long, default_value_t = false)] + pub dry_run: bool, } #[allow(clippy::struct_excessive_bools)] From 46e3fe0ab510e2ef06181c5efce114bd27e2fa73 Mon Sep 17 00:00:00 2001 From: marshmallow Date: Fri, 13 Mar 2026 10:54:25 +1100 Subject: [PATCH 2/2] remove clone bound on goal fn --- crates/cli/src/apply.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/apply.rs b/crates/cli/src/apply.rs index 3397508..bf92d15 100644 --- a/crates/cli/src/apply.rs +++ b/crates/cli/src/apply.rs @@ -100,7 +100,7 @@ pub async fn apply( modifiers: SubCommandModifiers, ) -> Result<()> where - F: Fn(&Name, &Node) -> Goal + Clone, + F: Fn(&Name, &Node) -> Goal, { let location = Arc::new(location);