1212//! would be the only consumer -- and in that case it's okay to query the
1313//! database directly.
1414
15+ // NOTE: eminates from Tabled macros
16+ #![ allow( clippy:: useless_vec) ]
17+
1518use crate :: Omdb ;
1619use anyhow:: anyhow;
1720use anyhow:: bail;
@@ -30,7 +33,9 @@ use nexus_db_model::DnsGroup;
3033use nexus_db_model:: DnsName ;
3134use nexus_db_model:: DnsVersion ;
3235use nexus_db_model:: DnsZone ;
36+ use nexus_db_model:: ExternalIp ;
3337use nexus_db_model:: Instance ;
38+ use nexus_db_model:: Project ;
3439use nexus_db_model:: Region ;
3540use nexus_db_model:: Sled ;
3641use nexus_db_model:: Zpool ;
@@ -86,6 +91,8 @@ enum DbCommands {
8691 Sleds ,
8792 /// Print information about customer instances
8893 Instances ,
94+ /// Print information about the network
95+ Network ( NetworkArgs ) ,
8996}
9097
9198#[ derive( Debug , Args ) ]
@@ -170,6 +177,22 @@ enum ServicesCommands {
170177 ListBySled ,
171178}
172179
180+ #[ derive( Debug , Args ) ]
181+ struct NetworkArgs {
182+ #[ command( subcommand) ]
183+ command : NetworkCommands ,
184+
185+ /// Print out raw data structures from the data store.
186+ #[ clap( long) ]
187+ verbose : bool ,
188+ }
189+
190+ #[ derive( Debug , Subcommand ) ]
191+ enum NetworkCommands {
192+ /// List external IPs
193+ ListEips ,
194+ }
195+
173196impl DbArgs {
174197 /// Run a `omdb db` subcommand.
175198 pub ( crate ) async fn run_cmd (
@@ -269,6 +292,13 @@ impl DbArgs {
269292 DbCommands :: Instances => {
270293 cmd_db_instances ( & datastore, self . fetch_limit ) . await
271294 }
295+ DbCommands :: Network ( NetworkArgs {
296+ command : NetworkCommands :: ListEips ,
297+ verbose,
298+ } ) => {
299+ cmd_db_eips ( & opctx, & datastore, self . fetch_limit , * verbose)
300+ . await
301+ }
272302 }
273303 }
274304}
@@ -1098,6 +1128,156 @@ async fn cmd_db_dns_names(
10981128 Ok ( ( ) )
10991129}
11001130
1131+ async fn cmd_db_eips (
1132+ opctx : & OpContext ,
1133+ datastore : & DataStore ,
1134+ limit : NonZeroU32 ,
1135+ verbose : bool ,
1136+ ) -> Result < ( ) , anyhow:: Error > {
1137+ use db:: schema:: external_ip:: dsl;
1138+ let ips: Vec < ExternalIp > = dsl:: external_ip
1139+ . filter ( dsl:: time_deleted. is_null ( ) )
1140+ . select ( ExternalIp :: as_select ( ) )
1141+ . get_results_async ( & * datastore. pool_connection_for_tests ( ) . await ?)
1142+ . await ?;
1143+
1144+ check_limit ( & ips, limit, || String :: from ( "listing external ips" ) ) ;
1145+
1146+ struct PortRange {
1147+ first : u16 ,
1148+ last : u16 ,
1149+ }
1150+
1151+ impl Display for PortRange {
1152+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
1153+ write ! ( f, "{}/{}" , self . first, self . last)
1154+ }
1155+ }
1156+
1157+ #[ derive( Tabled ) ]
1158+ enum Owner {
1159+ Instance { project : String , name : String } ,
1160+ Service { kind : String } ,
1161+ None ,
1162+ }
1163+
1164+ impl Display for Owner {
1165+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
1166+ match self {
1167+ Self :: Instance { project, name } => {
1168+ write ! ( f, "Instance {project}/{name}" )
1169+ }
1170+ Self :: Service { kind } => write ! ( f, "Service {kind}" ) ,
1171+ Self :: None => write ! ( f, "None" ) ,
1172+ }
1173+ }
1174+ }
1175+
1176+ #[ derive( Tabled ) ]
1177+ struct IpRow {
1178+ ip : ipnetwork:: IpNetwork ,
1179+ ports : PortRange ,
1180+ kind : String ,
1181+ owner : Owner ,
1182+ }
1183+
1184+ if verbose {
1185+ for ip in & ips {
1186+ if verbose {
1187+ println ! ( "{ip:#?}" ) ;
1188+ }
1189+ }
1190+ return Ok ( ( ) ) ;
1191+ }
1192+
1193+ let mut rows = Vec :: new ( ) ;
1194+
1195+ for ip in & ips {
1196+ let owner = if let Some ( owner_id) = ip. parent_id {
1197+ if ip. is_service {
1198+ let service = match LookupPath :: new ( opctx, datastore)
1199+ . service_id ( owner_id)
1200+ . fetch ( )
1201+ . await
1202+ {
1203+ Ok ( instance) => instance,
1204+ Err ( e) => {
1205+ eprintln ! (
1206+ "error looking up service with id {owner_id}: {e}"
1207+ ) ;
1208+ continue ;
1209+ }
1210+ } ;
1211+ Owner :: Service { kind : format ! ( "{:?}" , service. 1 . kind) }
1212+ } else {
1213+ use db:: schema:: instance:: dsl as instance_dsl;
1214+ let instance = match instance_dsl:: instance
1215+ . filter ( instance_dsl:: id. eq ( owner_id) )
1216+ . limit ( 1 )
1217+ . select ( Instance :: as_select ( ) )
1218+ . load_async ( & * datastore. pool_connection_for_tests ( ) . await ?)
1219+ . await
1220+ . context ( "loading requested instance" ) ?
1221+ . pop ( )
1222+ {
1223+ Some ( instance) => instance,
1224+ None => {
1225+ eprintln ! ( "instance with id {owner_id} not found" ) ;
1226+ continue ;
1227+ }
1228+ } ;
1229+
1230+ use db:: schema:: project:: dsl as project_dsl;
1231+ let project = match project_dsl:: project
1232+ . filter ( project_dsl:: id. eq ( instance. project_id ) )
1233+ . limit ( 1 )
1234+ . select ( Project :: as_select ( ) )
1235+ . load_async ( & * datastore. pool_connection_for_tests ( ) . await ?)
1236+ . await
1237+ . context ( "loading requested project" ) ?
1238+ . pop ( )
1239+ {
1240+ Some ( instance) => instance,
1241+ None => {
1242+ eprintln ! (
1243+ "project with id {} not found" ,
1244+ instance. project_id
1245+ ) ;
1246+ continue ;
1247+ }
1248+ } ;
1249+
1250+ Owner :: Instance {
1251+ project : project. name ( ) . to_string ( ) ,
1252+ name : instance. name ( ) . to_string ( ) ,
1253+ }
1254+ }
1255+ } else {
1256+ Owner :: None
1257+ } ;
1258+
1259+ let row = IpRow {
1260+ ip : ip. ip ,
1261+ ports : PortRange {
1262+ first : ip. first_port . into ( ) ,
1263+ last : ip. last_port . into ( ) ,
1264+ } ,
1265+ kind : format ! ( "{:?}" , ip. kind) ,
1266+ owner,
1267+ } ;
1268+ rows. push ( row) ;
1269+ }
1270+
1271+ rows. sort_by ( |a, b| a. ip . cmp ( & b. ip ) ) ;
1272+ let table = tabled:: Table :: new ( rows)
1273+ . with ( tabled:: settings:: Style :: empty ( ) )
1274+ . to_string ( ) ;
1275+
1276+ println ! ( "{}" , table) ;
1277+
1278+ Ok ( ( ) )
1279+ }
1280+
11011281fn print_name (
11021282 prefix : & str ,
11031283 name : & str ,
0 commit comments