m/c/metroctl: add exclude flag to node update

This allows excluding nodes from updates, required for doing update
sequencing manually on large clusters until we have an in-cluster
sequencer.

Change-Id: Ia522f55496b562781815aceb1321b6a2de93653d
Reviewed-on: https://review.monogon.dev/c/monogon/+/2985
Tested-by: Jenkins CI
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
diff --git a/metropolis/cli/metroctl/cmd_node.go b/metropolis/cli/metroctl/cmd_node.go
index 5332009..ebfaa25 100644
--- a/metropolis/cli/metroctl/cmd_node.go
+++ b/metropolis/cli/metroctl/cmd_node.go
@@ -127,6 +127,15 @@
 			}
 		}
 
+		excludedNodesSlice, err := cmd.Flags().GetStringArray("exclude")
+		if err != nil {
+			return err
+		}
+		excludedNodes := make(map[string]bool)
+		for _, n := range excludedNodesSlice {
+			excludedNodes[n] = true
+		}
+
 		updateReq := &apb.UpdateNodeRequest{
 			BundleUrl:      bundleUrl,
 			ActivationMode: am,
@@ -136,12 +145,15 @@
 
 		for _, n := range nodes {
 			// Filter the information we want client-side.
+			nid := identity.NodeID(n.Pubkey)
 			if len(qids) != 0 {
-				nid := identity.NodeID(n.Pubkey)
 				if _, e := qids[nid]; !e {
 					continue
 				}
 			}
+			if excludedNodes[nid] {
+				continue
+			}
 
 			if err := unavailableSemaphore.Acquire(ctx, 1); err != nil {
 				return err
@@ -265,6 +277,7 @@
 	nodeUpdateCmd.Flags().String("bundle-url", "", "The URL to the new version")
 	nodeUpdateCmd.Flags().String("activation-mode", "reboot", "How the update should be activated (kexec, reboot, none)")
 	nodeUpdateCmd.Flags().Uint64("max-unavailable", 1, "Maximum nodes which can be unavailable during the update process")
+	nodeUpdateCmd.Flags().StringArray("exclude", nil, "List of nodes to exclude (useful with the \"all\" argument)")
 
 	nodeDeleteCmd.Flags().Bool("bypass-has-roles", false, "Allows to bypass the HasRoles check")
 	nodeDeleteCmd.Flags().Bool("bypass-not-decommissioned", false, "Allows to bypass the NotDecommissioned check")