@@ -64,6 +64,7 @@ use omicron_uuid_kinds::SledUuid;
6464use omicron_uuid_kinds:: VnicUuid ;
6565use omicron_uuid_kinds:: { BlueprintUuid , MupdateOverrideUuid } ;
6666use omicron_uuid_kinds:: { CollectionUuid , MupdateUuid } ;
67+ use slog_error_chain:: InlineErrorChain ;
6768use std:: borrow:: Cow ;
6869use std:: collections:: BTreeSet ;
6970use std:: convert:: Infallible ;
@@ -312,6 +313,7 @@ fn process_command(
312313 Commands :: BlueprintShow ( args) => cmd_blueprint_show ( sim, args) ,
313314 Commands :: BlueprintDiff ( args) => cmd_blueprint_diff ( sim, args) ,
314315 Commands :: BlueprintDiffDns ( args) => cmd_blueprint_diff_dns ( sim, args) ,
316+ Commands :: BlueprintHistory ( args) => cmd_blueprint_history ( sim, args) ,
315317 Commands :: BlueprintSave ( args) => cmd_blueprint_save ( sim, args) ,
316318 Commands :: Show => cmd_show ( sim) ,
317319 Commands :: Set ( args) => cmd_set ( sim, args) ,
@@ -392,6 +394,12 @@ enum Commands {
392394 BlueprintDiff ( BlueprintDiffArgs ) ,
393395 /// show differences between a blueprint and a particular DNS version
394396 BlueprintDiffDns ( BlueprintDiffDnsArgs ) ,
397+ /// print a summary of the history of blueprints
398+ ///
399+ /// This is similar to `omdb reconfigurator history` in a live system, but
400+ /// it walks blueprints directly via their parent blueprint id rather than
401+ /// walking the `bp_target` table.
402+ BlueprintHistory ( BlueprintHistoryArgs ) ,
395403 /// write one blueprint to a file
396404 BlueprintSave ( BlueprintSaveArgs ) ,
397405
@@ -936,6 +944,16 @@ impl FromStr for BlueprintIdOpt {
936944 }
937945}
938946
947+ impl fmt:: Display for BlueprintIdOpt {
948+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
949+ match self {
950+ BlueprintIdOpt :: Target => f. write_str ( "target" ) ,
951+ BlueprintIdOpt :: Latest => f. write_str ( "latest" ) ,
952+ BlueprintIdOpt :: Id ( id) => id. fmt ( f) ,
953+ }
954+ }
955+ }
956+
939957impl From < BlueprintIdOpt > for BlueprintId {
940958 fn from ( value : BlueprintIdOpt ) -> Self {
941959 match value {
@@ -1200,6 +1218,21 @@ enum CliDnsGroup {
12001218 External ,
12011219}
12021220
1221+ #[ derive( Debug , Args ) ]
1222+ struct BlueprintHistoryArgs {
1223+ /// how many blueprints worth of history to report
1224+ #[ clap( long, default_value_t = 128 ) ]
1225+ limit : usize ,
1226+
1227+ /// also attempt to diff each blueprint
1228+ #[ clap( long, default_value_t = false ) ]
1229+ diff : bool ,
1230+
1231+ /// id of the blueprint to start history from
1232+ #[ clap( default_value_t = BlueprintIdOpt :: Target ) ]
1233+ blueprint_id : BlueprintIdOpt ,
1234+ }
1235+
12031236#[ derive( Debug , Args ) ]
12041237struct BlueprintSaveArgs {
12051238 /// id of the blueprint, "latest", or "target"
@@ -2609,6 +2642,92 @@ fn cmd_blueprint_diff_dns(
26092642 Ok ( Some ( dns_diff. to_string ( ) ) )
26102643}
26112644
2645+ // This command looks a lot like `omdb reconfigurator history`, but it differs
2646+ // in some ways that often don't matter but are worth knowing about:
2647+ //
2648+ // 1. It's reading the history of blueprints based on their parent ids. `omdb
2649+ // reconfigurator history` reads the `bp_target` table. These are generally
2650+ // equivalent, except that the `omdb` command sees entries for blueprints
2651+ // being enabled and disabled and the times that that happened. This command
2652+ // doesn't know that.
2653+ //
2654+ // 2. Relatedly, this command prints the creation time of the blueprint, not
2655+ // when it was made the target. These are generally very close in time but
2656+ // they're not the same thing.
2657+ fn cmd_blueprint_history (
2658+ sim : & mut ReconfiguratorSim ,
2659+ args : BlueprintHistoryArgs ,
2660+ ) -> anyhow:: Result < Option < String > > {
2661+ let BlueprintHistoryArgs { limit, diff, blueprint_id } = args;
2662+
2663+ let state = sim. current_state ( ) ;
2664+ let system = state. system ( ) ;
2665+ let resolved_id = system. resolve_blueprint_id ( blueprint_id. into ( ) ) ?;
2666+ let mut blueprint = system. get_blueprint ( & resolved_id) ?;
2667+
2668+ // We want to print the output in logical order, but in order to construct
2669+ // the output, we need to walk in reverse-logical order. To do this, we'll
2670+ // assemble the output as we go and then print it in reverse order.
2671+ //
2672+ // Although we're sort of printing a table, we use strings rather than a
2673+ // table in case we're in `diff` mode. In that case, we want to print
2674+ // details with each "row" that don't go in the table itself.
2675+ let mut entries = Vec :: new ( ) ;
2676+
2677+ while entries. len ( ) < limit {
2678+ let mut entry = String :: new ( ) ;
2679+ swriteln ! (
2680+ entry,
2681+ "{} {} {}" ,
2682+ humantime:: format_rfc3339_millis( blueprint. time_created. into( ) ) ,
2683+ blueprint. id,
2684+ blueprint. comment
2685+ ) ;
2686+ let new_blueprint = blueprint;
2687+ let Some ( parent_blueprint_id) = & blueprint. parent_blueprint_id else {
2688+ // We reached the initial blueprint.
2689+ entries. push ( entry) ;
2690+ break ;
2691+ } ;
2692+
2693+ blueprint = match system
2694+ . resolve_and_get_blueprint ( BlueprintId :: Id ( * parent_blueprint_id) )
2695+ {
2696+ Ok ( b) => b,
2697+ Err ( error) => {
2698+ swriteln ! (
2699+ entry,
2700+ "error walking back from blueprint {} to parent {}: {}" ,
2701+ blueprint. id,
2702+ parent_blueprint_id,
2703+ InlineErrorChain :: new( & error)
2704+ ) ;
2705+ entries. push ( entry) ;
2706+ break ;
2707+ }
2708+ } ;
2709+
2710+ if diff {
2711+ let diff = new_blueprint. diff_since_blueprint ( & blueprint) ;
2712+ swriteln ! ( entry, "{}" , diff. display( ) ) ;
2713+ }
2714+
2715+ entries. push ( entry) ;
2716+ }
2717+
2718+ if entries. len ( ) == limit {
2719+ entries. push ( String :: from ( "... (earlier history omitted)\n " ) ) ;
2720+ }
2721+
2722+ let mut output = String :: new ( ) ;
2723+ swriteln ! ( output, "{:24} {:36}" , "TIME" , "BLUEPRINT" ) ;
2724+ for entry in entries. iter ( ) . rev ( ) {
2725+ swrite ! ( output, "{entry}" ) ;
2726+ }
2727+
2728+ Ok ( Some ( output) )
2729+ }
2730+
26122731fn cmd_blueprint_save (
26132732 sim : & mut ReconfiguratorSim ,
26142733 args : BlueprintSaveArgs ,
0 commit comments