CliBootstrap.cs 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. using System.ComponentModel.DataAnnotations;
  2. using Microsoft.Extensions.Configuration;
  3. using Microsoft.Extensions.DependencyInjection;
  4. using RackPeek.Domain;
  5. using RackPeek.Domain.Helpers;
  6. using RackPeek.Domain.Persistence;
  7. using RackPeek.Domain.Persistence.Yaml;
  8. using RackPeek.Domain.Resources.AccessPoints;
  9. using RackPeek.Domain.Resources.Desktops;
  10. using RackPeek.Domain.Resources.Firewalls;
  11. using RackPeek.Domain.Resources.Laptops;
  12. using RackPeek.Domain.Resources.Routers;
  13. using RackPeek.Domain.Resources.Servers;
  14. using RackPeek.Domain.Resources.Services;
  15. using RackPeek.Domain.Resources.Switches;
  16. using RackPeek.Domain.Resources.SystemResources;
  17. using Shared.Rcl.Commands;
  18. using Shared.Rcl.Commands.AccessPoints;
  19. using Shared.Rcl.Commands.AccessPoints.Labels;
  20. using Shared.Rcl.Commands.AccessPoints.Rename;
  21. using Shared.Rcl.Commands.Connections;
  22. using Shared.Rcl.Commands.Desktops;
  23. using Shared.Rcl.Commands.Desktops.Cpus;
  24. using Shared.Rcl.Commands.Desktops.Drive;
  25. using Shared.Rcl.Commands.Desktops.Gpus;
  26. using Shared.Rcl.Commands.Desktops.Labels;
  27. using Shared.Rcl.Commands.Desktops.Nics;
  28. using Shared.Rcl.Commands.Desktops.Rename;
  29. using Shared.Rcl.Commands.Discovery;
  30. using Shared.Rcl.Commands.Exporters;
  31. using Shared.Rcl.Commands.Firewalls;
  32. using Shared.Rcl.Commands.Firewalls.Labels;
  33. using Shared.Rcl.Commands.Firewalls.Ports;
  34. using Shared.Rcl.Commands.Firewalls.Rename;
  35. using Shared.Rcl.Commands.Graph;
  36. using Shared.Rcl.Commands.Laptops;
  37. using Shared.Rcl.Commands.Laptops.Cpus;
  38. using Shared.Rcl.Commands.Laptops.Drive;
  39. using Shared.Rcl.Commands.Laptops.Gpus;
  40. using Shared.Rcl.Commands.Laptops.Labels;
  41. using Shared.Rcl.Commands.Laptops.Nics;
  42. using Shared.Rcl.Commands.Laptops.Rename;
  43. using Shared.Rcl.Commands.Routers;
  44. using Shared.Rcl.Commands.Routers.Labels;
  45. using Shared.Rcl.Commands.Routers.Ports;
  46. using Shared.Rcl.Commands.Routers.Rename;
  47. using Shared.Rcl.Commands.Servers;
  48. using Shared.Rcl.Commands.Servers.Cpus;
  49. using Shared.Rcl.Commands.Servers.Drives;
  50. using Shared.Rcl.Commands.Servers.Gpus;
  51. using Shared.Rcl.Commands.Servers.Labels;
  52. using Shared.Rcl.Commands.Servers.Nics;
  53. using Shared.Rcl.Commands.Servers.Rename;
  54. using Shared.Rcl.Commands.Services;
  55. using Shared.Rcl.Commands.Services.Labels;
  56. using Shared.Rcl.Commands.Services.Rename;
  57. using Shared.Rcl.Commands.Switches;
  58. using Shared.Rcl.Commands.Switches.Labels;
  59. using Shared.Rcl.Commands.Switches.Ports;
  60. using Shared.Rcl.Commands.Switches.Rename;
  61. using Shared.Rcl.Commands.Systems;
  62. using Shared.Rcl.Commands.Systems.Labels;
  63. using Shared.Rcl.Commands.Systems.Rename;
  64. using Shared.Rcl.Commands.OtherHardware;
  65. using Shared.Rcl.Commands.OtherHardware.Labels;
  66. using Shared.Rcl.Commands.OtherHardware.Ports;
  67. using Shared.Rcl.Commands.OtherHardware.Rename;
  68. using Shared.Rcl.Commands.Tags;
  69. using Shared.Rcl.Commands.Ups;
  70. using Shared.Rcl.Commands.Ups.Labels;
  71. using Shared.Rcl.Commands.Ups.Ports;
  72. using Shared.Rcl.Commands.Ups.Rename;
  73. using Spectre.Console;
  74. using Spectre.Console.Cli;
  75. namespace Shared.Rcl;
  76. public static class CliBootstrap {
  77. private static string[]? _lastArgs;
  78. private static CommandApp? _app;
  79. private static bool _showingHelp;
  80. public static void SetContext(string[] args, CommandApp app) {
  81. _lastArgs = args;
  82. _app = app;
  83. }
  84. public static async Task RegisterInternals(
  85. IServiceCollection services,
  86. IConfiguration configuration,
  87. string yamlDir,
  88. string yamlFile) {
  89. services.AddSingleton(configuration);
  90. var appBasePath = AppContext.BaseDirectory;
  91. // The store lives next to the binary, which is fine when rpk is run from its
  92. // own directory but not when it is dropped somewhere read-only — a container,
  93. // or /usr/local/bin as a non-root user. `rpk discover` is designed to run on
  94. // machines that hold no inventory at all, so an unavailable store must not stop
  95. // the process starting; commands that actually need one fail when they use it.
  96. var resolvedYamlDir = Path.IsPathRooted(yamlDir)
  97. ? yamlDir
  98. : Path.Combine(appBasePath, yamlDir);
  99. var fullYamlPath = Path.Combine(resolvedYamlDir, yamlFile);
  100. try {
  101. Directory.CreateDirectory(resolvedYamlDir);
  102. if (!File.Exists(fullYamlPath)) await File.WriteAllTextAsync(fullYamlPath, "");
  103. }
  104. catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) {
  105. await System.Console.Error.WriteLineAsync(
  106. $"Warning: cannot use the config at {fullYamlPath} ({ex.Message}). " +
  107. "Continuing with an empty inventory — reads will show nothing, and " +
  108. "writes are refused until the config can be read.");
  109. }
  110. services.AddLogging();
  111. services.AddScoped<RackPeekConfigMigrationDeserializer>();
  112. services.AddScoped<IResourceYamlMigrationService, ResourceYamlMigrationService>();
  113. ServiceProvider b = services.BuildServiceProvider();
  114. var collection = new YamlResourceCollection(
  115. fullYamlPath,
  116. new PhysicalTextFileStore(),
  117. new ResourceCollection(),
  118. b.GetRequiredService<IResourceYamlMigrationService>());
  119. try {
  120. await collection.LoadAsync();
  121. }
  122. catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) {
  123. // Unreadable store, warned about above. A malformed config is a different
  124. // matter and is still allowed to fail loudly — the user has one to fix.
  125. await System.Console.Error.WriteLineAsync($"Warning: could not read {fullYamlPath} ({ex.Message}).");
  126. }
  127. catch (ConfigLoadException) {
  128. // A damaged config must not stop the process starting — `rpk discover` and
  129. // `--help` do not need the inventory, and the web UI is how someone fixes
  130. // the file. The failure is recorded on the collection, so every command
  131. // that does touch the inventory fails with it instead of reporting an
  132. // empty one (#337).
  133. }
  134. services.AddSingleton<IResourceCollection>(collection);
  135. // Infrastructure
  136. services.AddYamlRepos();
  137. // Application (also registers the discovery probes, for every host)
  138. services.AddUseCases();
  139. services.AddCommands();
  140. }
  141. public static void BuildApp(CommandApp app) {
  142. // Spectre bootstrap
  143. app.Configure(config => {
  144. config.SetApplicationName("rpk");
  145. config.ValidateExamples();
  146. config.SetApplicationVersion(RpkConstants.Version);
  147. config.SetExceptionHandler(HandleException);
  148. // Global summary
  149. config.AddCommand<GetTotalSummaryCommand>("summary")
  150. .WithDescription("Show a summarized report of all resources in the system.");
  151. // ----------------------------
  152. // Server commands (CRUD-style)
  153. // ----------------------------
  154. config.AddBranch("servers", server => {
  155. server.SetDescription("Manage servers and their components.");
  156. server.AddCommand<ServerReportCommand>("summary")
  157. .WithDescription("Show a summarized hardware report for all servers.");
  158. server.AddCommand<ServerAddCommand>("add").WithDescription("Add a new server to the inventory.");
  159. server.AddCommand<ServerGetByNameCommand>("get")
  160. .WithDescription("List all servers or retrieve a specific server by name.");
  161. server.AddCommand<ServerDescribeCommand>("describe")
  162. .WithDescription("Display detailed information about a specific server.");
  163. server.AddCommand<ServerSetCommand>("set").WithDescription("Update properties of an existing server.");
  164. server.AddCommand<ServerDeleteCommand>("del").WithDescription("Delete a server from the inventory.");
  165. server.AddCommand<ServerRenameCommand>("rename")
  166. .WithDescription("Rename a server to a new name.");
  167. server.AddCommand<ServerTreeCommand>("tree")
  168. .WithDescription("Display the dependency tree of a server.");
  169. // Server CPUs
  170. server.AddBranch("cpu", cpu => {
  171. cpu.SetDescription("Manage CPUs attached to a server.");
  172. cpu.AddCommand<ServerCpuAddCommand>("add").WithDescription("Add a CPU to a specific server.");
  173. cpu.AddCommand<ServerCpuSetCommand>("set").WithDescription("Update configuration of a server CPU.");
  174. cpu.AddCommand<ServerCpuRemoveCommand>("del").WithDescription("Remove a CPU from a server.");
  175. });
  176. // Server Drives
  177. server.AddBranch("drive", drive => {
  178. drive.SetDescription("Manage drives attached to a server.");
  179. drive.AddCommand<ServerDriveAddCommand>("add").WithDescription("Add a storage drive to a server.");
  180. drive.AddCommand<ServerDriveUpdateCommand>("set")
  181. .WithDescription("Update properties of a server drive.");
  182. drive.AddCommand<ServerDriveRemoveCommand>("del").WithDescription("Remove a drive from a server.");
  183. });
  184. // Server GPUs
  185. server.AddBranch("gpu", gpu => {
  186. gpu.SetDescription("Manage GPUs attached to a server.");
  187. gpu.AddCommand<ServerGpuAddCommand>("add").WithDescription("Add a GPU to a server.");
  188. gpu.AddCommand<ServerGpuUpdateCommand>("set").WithDescription("Update properties of a server GPU.");
  189. gpu.AddCommand<ServerGpuRemoveCommand>("del").WithDescription("Remove a GPU from a server.");
  190. });
  191. // Server NICs
  192. server.AddBranch("nic", nic => {
  193. nic.SetDescription("Manage network interface cards (NICs) for a server.");
  194. nic.AddCommand<ServerNicAddCommand>("add").WithDescription("Add a NIC to a server.");
  195. nic.AddCommand<ServerNicUpdateCommand>("set").WithDescription("Update properties of a server NIC.");
  196. nic.AddCommand<ServerNicRemoveCommand>("del").WithDescription("Remove a NIC from a server.");
  197. });
  198. // Server Labels
  199. server.AddBranch("label", label => {
  200. label.SetDescription("Manage labels on a server.");
  201. label.AddCommand<ServerLabelAddCommand>("add").WithDescription("Add a label to a server.");
  202. label.AddCommand<ServerLabelRemoveCommand>("remove")
  203. .WithDescription("Remove a label from a server.");
  204. });
  205. // Server Tags
  206. server.AddBranch("tag", tag => {
  207. tag.SetDescription("Manage tags on a server.");
  208. tag.AddCommand<TagAddCommand<Server>>("add").WithDescription("Add a tag to a server.");
  209. tag.AddCommand<TagRemoveCommand<Server>>("remove").WithDescription("Remove a tag from a server.");
  210. });
  211. });
  212. // ----------------------------
  213. // Switch commands
  214. // ----------------------------
  215. config.AddBranch("switches", switches => {
  216. switches.SetDescription("Manage network switches.");
  217. switches.AddCommand<SwitchReportCommand>("summary")
  218. .WithDescription("Show a hardware report for all switches.");
  219. switches.AddCommand<SwitchAddCommand>("add")
  220. .WithDescription("Add a new network switch to the inventory.");
  221. switches.AddCommand<SwitchGetCommand>("list").WithDescription("List all switches in the system.");
  222. switches.AddCommand<SwitchGetByNameCommand>("get")
  223. .WithDescription("Retrieve details of a specific switch by name.");
  224. switches.AddCommand<SwitchDescribeCommand>("describe")
  225. .WithDescription("Show detailed information about a switch.");
  226. switches.AddCommand<SwitchSetCommand>("set").WithDescription("Update properties of a switch.");
  227. switches.AddCommand<SwitchDeleteCommand>("del").WithDescription("Delete a switch from the inventory.");
  228. switches.AddCommand<SwitchRenameCommand>("rename")
  229. .WithDescription("Rename a switch to a new name.");
  230. switches.AddBranch("port", port => {
  231. port.SetDescription("Manage ports on a network switch.");
  232. port.AddCommand<SwitchPortAddCommand>("add").WithDescription("Add a port to a switch.");
  233. port.AddCommand<SwitchPortUpdateCommand>("set").WithDescription("Update a switch port.");
  234. port.AddCommand<SwitchPortRemoveCommand>("del").WithDescription("Remove a port from a switch.");
  235. });
  236. switches.AddBranch("label", label => {
  237. label.SetDescription("Manage labels on a switch.");
  238. label.AddCommand<SwitchLabelAddCommand>("add").WithDescription("Add a label to a switch.");
  239. label.AddCommand<SwitchLabelRemoveCommand>("remove")
  240. .WithDescription("Remove a label from a switch.");
  241. });
  242. switches.AddBranch("tag", tag => {
  243. tag.SetDescription("Manage tags on a switch.");
  244. tag.AddCommand<TagAddCommand<Switch>>("add").WithDescription("Add a tag to a switch.");
  245. tag.AddCommand<TagRemoveCommand<Switch>>("remove").WithDescription("Remove a tag from a switch.");
  246. });
  247. });
  248. // ----------------------------
  249. // Routers commands
  250. // ----------------------------
  251. config.AddBranch("routers", routers => {
  252. routers.SetDescription("Manage network routers.");
  253. routers.AddCommand<RouterReportCommand>("summary")
  254. .WithDescription("Show a hardware report for all routers.");
  255. routers.AddCommand<RouterAddCommand>("add")
  256. .WithDescription("Add a new network router to the inventory.");
  257. routers.AddCommand<RouterGetCommand>("list").WithDescription("List all routers in the system.");
  258. routers.AddCommand<RouterGetByNameCommand>("get")
  259. .WithDescription("Retrieve details of a specific router by name.");
  260. routers.AddCommand<RouterDescribeCommand>("describe")
  261. .WithDescription("Show detailed information about a router.");
  262. routers.AddCommand<RouterSetCommand>("set").WithDescription("Update properties of a router.");
  263. routers.AddCommand<RouterDeleteCommand>("del").WithDescription("Delete a router from the inventory.");
  264. routers.AddCommand<RouterRenameCommand>("rename")
  265. .WithDescription("Rename a router to a new name.");
  266. routers.AddBranch("port", port => {
  267. port.SetDescription("Manage ports on a router.");
  268. port.AddCommand<RouterPortAddCommand>("add").WithDescription("Add a port to a router.");
  269. port.AddCommand<RouterPortUpdateCommand>("set").WithDescription("Update a router port.");
  270. port.AddCommand<RouterPortRemoveCommand>("del").WithDescription("Remove a port from a router.");
  271. });
  272. routers.AddBranch("label", label => {
  273. label.SetDescription("Manage labels on a router.");
  274. label.AddCommand<RouterLabelAddCommand>("add").WithDescription("Add a label to a router.");
  275. label.AddCommand<RouterLabelRemoveCommand>("remove")
  276. .WithDescription("Remove a label from a router.");
  277. });
  278. routers.AddBranch("tag", tag => {
  279. tag.SetDescription("Manage tags on a router.");
  280. tag.AddCommand<TagAddCommand<Router>>("add").WithDescription("Add a tag to a router.");
  281. tag.AddCommand<TagRemoveCommand<Router>>("remove").WithDescription("Remove a tag from a router.");
  282. });
  283. });
  284. // ----------------------------
  285. // Firewalls commands
  286. // ----------------------------
  287. config.AddBranch("firewalls", firewalls => {
  288. firewalls.SetDescription("Manage firewalls.");
  289. firewalls.AddCommand<FirewallReportCommand>("summary")
  290. .WithDescription("Show a hardware report for all firewalls.");
  291. firewalls.AddCommand<FirewallAddCommand>("add").WithDescription("Add a new firewall to the inventory.");
  292. firewalls.AddCommand<FirewallGetCommand>("list").WithDescription("List all firewalls in the system.");
  293. firewalls.AddCommand<FirewallGetByNameCommand>("get")
  294. .WithDescription("Retrieve details of a specific firewall by name.");
  295. firewalls.AddCommand<FirewallDescribeCommand>("describe")
  296. .WithDescription("Show detailed information about a firewall.");
  297. firewalls.AddCommand<FirewallSetCommand>("set").WithDescription("Update properties of a firewall.");
  298. firewalls.AddCommand<FirewallDeleteCommand>("del")
  299. .WithDescription("Delete a firewall from the inventory.");
  300. firewalls.AddCommand<FirewallRenameCommand>("rename")
  301. .WithDescription("Rename a firewall to a new name.");
  302. firewalls.AddBranch("port", port => {
  303. port.SetDescription("Manage ports on a firewall.");
  304. port.AddCommand<FirewallPortAddCommand>("add").WithDescription("Add a port to a firewall.");
  305. port.AddCommand<FirewallPortUpdateCommand>("set").WithDescription("Update a firewall port.");
  306. port.AddCommand<FirewallPortRemoveCommand>("del").WithDescription("Remove a port from a firewall.");
  307. });
  308. firewalls.AddBranch("label", label => {
  309. label.SetDescription("Manage labels on a firewall.");
  310. label.AddCommand<FirewallLabelAddCommand>("add").WithDescription("Add a label to a firewall.");
  311. label.AddCommand<FirewallLabelRemoveCommand>("remove")
  312. .WithDescription("Remove a label from a firewall.");
  313. });
  314. firewalls.AddBranch("tag", tag => {
  315. tag.SetDescription("Manage tags on a firewall.");
  316. tag.AddCommand<TagAddCommand<Firewall>>("add").WithDescription("Add a tag to a firewall.");
  317. tag.AddCommand<TagRemoveCommand<Firewall>>("remove")
  318. .WithDescription("Remove a tag from a firewall.");
  319. });
  320. });
  321. // ----------------------------
  322. // System commands
  323. // ----------------------------
  324. config.AddBranch("systems", system => {
  325. system.SetDescription("Manage systems and their dependencies.");
  326. system.AddCommand<SystemReportCommand>("summary")
  327. .WithDescription("Show a summary report for all systems.");
  328. system.AddCommand<SystemAddCommand>("add").WithDescription("Add a new system to the inventory.");
  329. system.AddCommand<SystemGetCommand>("list").WithDescription("List all systems.");
  330. system.AddCommand<SystemGetByNameCommand>("get").WithDescription("Retrieve a system by name.");
  331. system.AddCommand<SystemDescribeCommand>("describe")
  332. .WithDescription("Display detailed information about a system.");
  333. system.AddCommand<SystemSetCommand>("set").WithDescription("Update properties of a system.");
  334. system.AddCommand<SystemDeleteCommand>("del").WithDescription("Delete a system from the inventory.");
  335. system.AddCommand<SystemRenameCommand>("rename")
  336. .WithDescription("Rename a system to a new name.");
  337. system.AddCommand<SystemTreeCommand>("tree")
  338. .WithDescription("Display the dependency tree for a system.");
  339. system.AddBranch("label", label => {
  340. label.SetDescription("Manage labels on a system.");
  341. label.AddCommand<SystemLabelAddCommand>("add").WithDescription("Add a label to a system.");
  342. label.AddCommand<SystemLabelRemoveCommand>("remove")
  343. .WithDescription("Remove a label from a system.");
  344. });
  345. system.AddBranch("tag", tag => {
  346. tag.SetDescription("Manage tags on a system.");
  347. tag.AddCommand<TagAddCommand<SystemResource>>("add").WithDescription("Add a tag to a system.");
  348. tag.AddCommand<TagRemoveCommand<SystemResource>>("remove")
  349. .WithDescription("Remove a tag from a system.");
  350. });
  351. });
  352. // ----------------------------
  353. // Access Points
  354. // ----------------------------
  355. config.AddBranch("accesspoints", ap => {
  356. ap.SetDescription("Manage access points.");
  357. ap.AddCommand<AccessPointReportCommand>("summary")
  358. .WithDescription("Show a hardware report for all access points.");
  359. ap.AddCommand<AccessPointAddCommand>("add").WithDescription("Add a new access point.");
  360. ap.AddCommand<AccessPointGetCommand>("list").WithDescription("List all access points.");
  361. ap.AddCommand<AccessPointGetByNameCommand>("get").WithDescription("Retrieve an access point by name.");
  362. ap.AddCommand<AccessPointDescribeCommand>("describe")
  363. .WithDescription("Show detailed information about an access point.");
  364. ap.AddCommand<AccessPointSetCommand>("set").WithDescription("Update properties of an access point.");
  365. ap.AddCommand<AccessPointDeleteCommand>("del").WithDescription("Delete an access point.");
  366. ap.AddCommand<AccessPointRenameCommand>("rename")
  367. .WithDescription("Rename an access point to a new name.");
  368. ap.AddBranch("label", label => {
  369. label.SetDescription("Manage labels on an access point.");
  370. label.AddCommand<AccessPointLabelAddCommand>("add")
  371. .WithDescription("Add a label to an access point.");
  372. label.AddCommand<AccessPointLabelRemoveCommand>("remove")
  373. .WithDescription("Remove a label from an access point.");
  374. });
  375. ap.AddBranch("tag", tag => {
  376. tag.SetDescription("Manage tags on an access point.");
  377. tag.AddCommand<TagAddCommand<AccessPoint>>("add")
  378. .WithDescription("Add a tag to an access point.");
  379. tag.AddCommand<TagRemoveCommand<AccessPoint>>("remove")
  380. .WithDescription("Remove a tag from an access point.");
  381. });
  382. });
  383. // ----------------------------
  384. // UPS units
  385. // ----------------------------
  386. config.AddBranch("ups", ups => {
  387. ups.SetDescription("Manage UPS units.");
  388. ups.AddCommand<UpsReportCommand>("summary")
  389. .WithDescription("Show a hardware report for all UPS units.");
  390. ups.AddCommand<UpsAddCommand>("add").WithDescription("Add a new UPS unit.");
  391. ups.AddCommand<UpsGetCommand>("list").WithDescription("List all UPS units.");
  392. ups.AddCommand<UpsGetByNameCommand>("get").WithDescription("Retrieve a UPS unit by name.");
  393. ups.AddCommand<UpsDescribeCommand>("describe")
  394. .WithDescription("Show detailed information about a UPS unit.");
  395. ups.AddCommand<UpsSetCommand>("set").WithDescription("Update properties of a UPS unit.");
  396. ups.AddCommand<UpsDeleteCommand>("del").WithDescription("Delete a UPS unit.");
  397. ups.AddCommand<UpsRenameCommand>("rename")
  398. .WithDescription("Rename a UPS unit to a new name.");
  399. ups.AddBranch("port", port => {
  400. port.SetDescription("Manage ports on a UPS unit.");
  401. port.AddCommand<UpsPortAddCommand>("add").WithDescription("Add a port to a UPS unit.");
  402. port.AddCommand<UpsPortUpdateCommand>("set").WithDescription("Update a UPS unit port.");
  403. port.AddCommand<UpsPortRemoveCommand>("del").WithDescription("Remove a port from a UPS unit.");
  404. });
  405. ups.AddBranch("label", label => {
  406. label.SetDescription("Manage labels on a UPS unit.");
  407. label.AddCommand<UpsLabelAddCommand>("add").WithDescription("Add a label to a UPS unit.");
  408. label.AddCommand<UpsLabelRemoveCommand>("remove")
  409. .WithDescription("Remove a label from a UPS unit.");
  410. });
  411. ups.AddBranch("tag", tag => {
  412. tag.SetDescription("Manage tags on a UPS unit.");
  413. tag.AddCommand<TagAddCommand<RackPeek.Domain.Resources.UpsUnits.Ups>>("add")
  414. .WithDescription("Add a tag to a UPS unit.");
  415. tag.AddCommand<TagRemoveCommand<RackPeek.Domain.Resources.UpsUnits.Ups>>("remove")
  416. .WithDescription("Remove a tag from a UPS unit.");
  417. });
  418. });
  419. // ----------------------------
  420. // Other hardware
  421. // ----------------------------
  422. config.AddBranch("other", other => {
  423. other.SetDescription("Manage other hardware that doesn't fit an existing category.");
  424. other.AddCommand<OtherReportCommand>("summary")
  425. .WithDescription("Show a hardware report for all other hardware.");
  426. other.AddCommand<OtherAddCommand>("add").WithDescription("Add new other hardware.");
  427. other.AddCommand<OtherGetCommand>("list").WithDescription("List all other hardware.");
  428. other.AddCommand<OtherGetByNameCommand>("get").WithDescription("Retrieve other hardware by name.");
  429. other.AddCommand<OtherDescribeCommand>("describe")
  430. .WithDescription("Show detailed information about other hardware.");
  431. other.AddCommand<OtherSetCommand>("set").WithDescription("Update properties of other hardware.");
  432. other.AddCommand<OtherDeleteCommand>("del").WithDescription("Delete other hardware.");
  433. other.AddCommand<OtherRenameCommand>("rename")
  434. .WithDescription("Rename other hardware to a new name.");
  435. other.AddBranch("port", port => {
  436. port.SetDescription("Manage ports on other hardware.");
  437. port.AddCommand<OtherPortAddCommand>("add").WithDescription("Add a port to other hardware.");
  438. port.AddCommand<OtherPortUpdateCommand>("set").WithDescription("Update an other hardware port.");
  439. port.AddCommand<OtherPortRemoveCommand>("del")
  440. .WithDescription("Remove a port from other hardware.");
  441. });
  442. other.AddBranch("label", label => {
  443. label.SetDescription("Manage labels on other hardware.");
  444. label.AddCommand<OtherLabelAddCommand>("add").WithDescription("Add a label to other hardware.");
  445. label.AddCommand<OtherLabelRemoveCommand>("remove")
  446. .WithDescription("Remove a label from other hardware.");
  447. });
  448. other.AddBranch("tag", tag => {
  449. tag.SetDescription("Manage tags on other hardware.");
  450. tag.AddCommand<TagAddCommand<RackPeek.Domain.Resources.OtherHardware.Other>>("add")
  451. .WithDescription("Add a tag to other hardware.");
  452. tag.AddCommand<TagRemoveCommand<RackPeek.Domain.Resources.OtherHardware.Other>>("remove")
  453. .WithDescription("Remove a tag from other hardware.");
  454. });
  455. });
  456. // ----------------------------
  457. // Desktops
  458. // ----------------------------
  459. config.AddBranch("desktops", desktops => {
  460. desktops.SetDescription("Manage desktop computers and their components.");
  461. // CRUD
  462. desktops.AddCommand<DesktopAddCommand>("add").WithDescription("Add a new desktop.");
  463. desktops.AddCommand<DesktopGetCommand>("list").WithDescription("List all desktops.");
  464. desktops.AddCommand<DesktopGetByNameCommand>("get").WithDescription("Retrieve a desktop by name.");
  465. desktops.AddCommand<DesktopDescribeCommand>("describe")
  466. .WithDescription("Show detailed information about a desktop.");
  467. desktops.AddCommand<DesktopSetCommand>("set").WithDescription("Update properties of a desktop.");
  468. desktops.AddCommand<DesktopDeleteCommand>("del")
  469. .WithDescription("Delete a desktop from the inventory.");
  470. desktops.AddCommand<DesktopRenameCommand>("rename")
  471. .WithDescription("Rename a desktop to a new name.");
  472. desktops.AddCommand<DesktopReportCommand>("summary")
  473. .WithDescription("Show a summarized hardware report for all desktops.");
  474. desktops.AddCommand<DesktopTreeCommand>("tree")
  475. .WithDescription("Display the dependency tree for a desktop.");
  476. // CPU
  477. desktops.AddBranch("cpu", cpu => {
  478. cpu.SetDescription("Manage CPUs attached to desktops.");
  479. cpu.AddCommand<DesktopCpuAddCommand>("add").WithDescription("Add a CPU to a desktop.");
  480. cpu.AddCommand<DesktopCpuSetCommand>("set").WithDescription("Update a desktop CPU.");
  481. cpu.AddCommand<DesktopCpuRemoveCommand>("del").WithDescription("Remove a CPU from a desktop.");
  482. });
  483. // Drives
  484. desktops.AddBranch("drive", drive => {
  485. drive.SetDescription("Manage storage drives attached to desktops.");
  486. drive.AddCommand<DesktopDriveAddCommand>("add").WithDescription("Add a drive to a desktop.");
  487. drive.AddCommand<DesktopDriveSetCommand>("set").WithDescription("Update a desktop drive.");
  488. drive.AddCommand<DesktopDriveRemoveCommand>("del")
  489. .WithDescription("Remove a drive from a desktop.");
  490. });
  491. // GPUs
  492. desktops.AddBranch("gpu", gpu => {
  493. gpu.SetDescription("Manage GPUs attached to desktops.");
  494. gpu.AddCommand<DesktopGpuAddCommand>("add").WithDescription("Add a GPU to a desktop.");
  495. gpu.AddCommand<DesktopGpuSetCommand>("set").WithDescription("Update a desktop GPU.");
  496. gpu.AddCommand<DesktopGpuRemoveCommand>("del").WithDescription("Remove a GPU from a desktop.");
  497. });
  498. // NICs
  499. desktops.AddBranch("nic", nic => {
  500. nic.SetDescription("Manage network interface cards (NICs) for desktops.");
  501. nic.AddCommand<DesktopNicAddCommand>("add").WithDescription("Add a NIC to a desktop.");
  502. nic.AddCommand<DesktopNicSetCommand>("set").WithDescription("Update a desktop NIC.");
  503. nic.AddCommand<DesktopNicRemoveCommand>("del").WithDescription("Remove a NIC from a desktop.");
  504. });
  505. desktops.AddBranch("label", label => {
  506. label.SetDescription("Manage labels on a desktop.");
  507. label.AddCommand<DesktopLabelAddCommand>("add").WithDescription("Add a label to a desktop.");
  508. label.AddCommand<DesktopLabelRemoveCommand>("remove")
  509. .WithDescription("Remove a label from a desktop.");
  510. });
  511. desktops.AddBranch("tag", tag => {
  512. tag.SetDescription("Manage tags on a desktop.");
  513. tag.AddCommand<TagAddCommand<Desktop>>("add").WithDescription("Add a tag to a desktop.");
  514. tag.AddCommand<TagRemoveCommand<Desktop>>("remove")
  515. .WithDescription("Remove a tag from a desktop.");
  516. });
  517. });
  518. // ----------------------------
  519. // Laptops
  520. // ----------------------------
  521. config.AddBranch("laptops", laptops => {
  522. laptops.SetDescription("Manage Laptop computers and their components.");
  523. // CRUD
  524. laptops.AddCommand<LaptopAddCommand>("add").WithDescription("Add a new Laptop.");
  525. laptops.AddCommand<LaptopGetCommand>("list").WithDescription("List all Laptops.");
  526. laptops.AddCommand<LaptopGetByNameCommand>("get").WithDescription("Retrieve a Laptop by name.");
  527. laptops.AddCommand<LaptopDescribeCommand>("describe")
  528. .WithDescription("Show detailed information about a Laptop.");
  529. laptops.AddCommand<LaptopSetCommand>("set").WithDescription("Update properties of a laptop.");
  530. laptops.AddCommand<LaptopDeleteCommand>("del").WithDescription("Delete a Laptop from the inventory.");
  531. laptops.AddCommand<LaptopRenameCommand>("rename")
  532. .WithDescription("Rename a Laptop to a new name.");
  533. laptops.AddCommand<LaptopReportCommand>("summary")
  534. .WithDescription("Show a summarized hardware report for all Laptops.");
  535. laptops.AddCommand<LaptopTreeCommand>("tree")
  536. .WithDescription("Display the dependency tree for a Laptop.");
  537. // CPU
  538. laptops.AddBranch("cpu", cpu => {
  539. cpu.SetDescription("Manage CPUs attached to Laptops.");
  540. cpu.AddCommand<LaptopCpuAddCommand>("add").WithDescription("Add a CPU to a Laptop.");
  541. cpu.AddCommand<LaptopCpuSetCommand>("set").WithDescription("Update a Laptop CPU.");
  542. cpu.AddCommand<LaptopCpuRemoveCommand>("del").WithDescription("Remove a CPU from a Laptop.");
  543. });
  544. // Drives
  545. laptops.AddBranch("drive", drive => {
  546. drive.SetDescription("Manage storage drives attached to Laptops.");
  547. drive.AddCommand<LaptopDriveAddCommand>("add").WithDescription("Add a drive to a Laptop.");
  548. drive.AddCommand<LaptopDriveSetCommand>("set").WithDescription("Update a Laptop drive.");
  549. drive.AddCommand<LaptopDriveRemoveCommand>("del").WithDescription("Remove a drive from a Laptop.");
  550. });
  551. // GPUs
  552. laptops.AddBranch("gpu", gpu => {
  553. gpu.SetDescription("Manage GPUs attached to Laptops.");
  554. gpu.AddCommand<LaptopGpuAddCommand>("add").WithDescription("Add a GPU to a Laptop.");
  555. gpu.AddCommand<LaptopGpuSetCommand>("set").WithDescription("Update a Laptop GPU.");
  556. gpu.AddCommand<LaptopGpuRemoveCommand>("del").WithDescription("Remove a GPU from a Laptop.");
  557. });
  558. // NICs
  559. laptops.AddBranch("nic", nic => {
  560. nic.SetDescription("Manage network interface cards (NICs) for Laptops.");
  561. nic.AddCommand<LaptopNicAddCommand>("add").WithDescription("Add a NIC to a Laptop.");
  562. nic.AddCommand<LaptopNicSetCommand>("set").WithDescription("Update a Laptop NIC.");
  563. nic.AddCommand<LaptopNicRemoveCommand>("del").WithDescription("Remove a NIC from a Laptop.");
  564. });
  565. laptops.AddBranch("label", label => {
  566. label.SetDescription("Manage labels on a laptop.");
  567. label.AddCommand<LaptopLabelAddCommand>("add").WithDescription("Add a label to a laptop.");
  568. label.AddCommand<LaptopLabelRemoveCommand>("remove")
  569. .WithDescription("Remove a label from a laptop.");
  570. });
  571. laptops.AddBranch("tag", tag => {
  572. tag.SetDescription("Manage tags on a laptop.");
  573. tag.AddCommand<TagAddCommand<Laptop>>("add").WithDescription("Add a tag to a laptop.");
  574. tag.AddCommand<TagRemoveCommand<Laptop>>("remove")
  575. .WithDescription("Remove a tag from a laptop.");
  576. });
  577. });
  578. // ----------------------------
  579. // Services
  580. // ----------------------------
  581. config.AddBranch("services", service => {
  582. service.SetDescription("Manage services and their configurations.");
  583. service.AddCommand<ServiceReportCommand>("summary")
  584. .WithDescription("Show a summary report for all services.");
  585. service.AddCommand<ServiceAddCommand>("add").WithDescription("Add a new service.");
  586. service.AddCommand<ServiceGetCommand>("list").WithDescription("List all services.");
  587. service.AddCommand<ServiceGetByNameCommand>("get").WithDescription("Retrieve a service by name.");
  588. service.AddCommand<ServiceDescribeCommand>("describe")
  589. .WithDescription("Show detailed information about a service.");
  590. service.AddCommand<ServiceSetCommand>("set").WithDescription("Update properties of a service.");
  591. service.AddCommand<ServiceDeleteCommand>("del").WithDescription("Delete a service.");
  592. service.AddCommand<ServiceRenameCommand>("rename")
  593. .WithDescription("Rename a service to a new name.");
  594. service.AddCommand<ServiceSubnetsCommand>("subnets")
  595. .WithDescription("List subnets associated with a service, optionally filtered by CIDR.");
  596. service.AddBranch("label", label => {
  597. label.SetDescription("Manage labels on a service.");
  598. label.AddCommand<ServiceLabelAddCommand>("add").WithDescription("Add a label to a service.");
  599. label.AddCommand<ServiceLabelRemoveCommand>("remove")
  600. .WithDescription("Remove a label from a service.");
  601. });
  602. service.AddBranch("tag", tag => {
  603. tag.SetDescription("Manage tags on a service.");
  604. tag.AddCommand<TagAddCommand<Service>>("add").WithDescription("Add a tag to a service.");
  605. tag.AddCommand<TagRemoveCommand<Service>>("remove")
  606. .WithDescription("Remove a tag from a service.");
  607. });
  608. });
  609. // ----------------------------
  610. // Ansible
  611. // ----------------------------
  612. config.AddBranch("discover", discover => {
  613. discover.SetDescription("Read infrastructure and emit it as RackPeek YAML.");
  614. discover.AddCommand<DiscoverSystemCommand>("system")
  615. .WithDescription("Inspect this machine and emit it as a System resource.")
  616. .WithExample("discover", "system")
  617. .WithExample("discover", "system", "--name", "nas01", "--push");
  618. discover.AddCommand<DiscoverDockerCommand>("docker")
  619. .WithDescription("Read the Docker API and emit each published container as a Service on this host's System.")
  620. .WithExample("discover", "docker")
  621. .WithExample("discover", "docker", "--push");
  622. discover.AddCommand<DiscoverProxmoxCommand>("proxmox")
  623. .WithDescription("Read a Proxmox cluster and emit its nodes and guests as Systems.")
  624. .WithExample("discover", "proxmox", "--host", "https://pve.lan:8006", "--insecure")
  625. .WithExample("discover", "proxmox", "--host", "pve.lan", "--push");
  626. discover.AddCommand<DiscoverNetworkCommand>("network")
  627. .WithDescription("Sweep a subnet and emit every answering host as a System resource.")
  628. .WithExample("discover", "network")
  629. .WithExample("discover", "network", "--cidr", "192.168.1.0/24")
  630. .WithExample("discover", "network", "--cidr", "10.0.0.0/24", "--ports", "22,80,443", "--push");
  631. });
  632. config.AddBranch("ansible", ansible => {
  633. ansible.SetDescription("Generate and manage Ansible inventory.");
  634. ansible.AddCommand<GenerateAnsibleInventoryCommand>("inventory")
  635. .WithDescription("Generate an Ansible inventory.");
  636. });
  637. config.AddBranch("ssh", ssh => {
  638. ssh.SetDescription("Generate SSH configuration from infrastructure.");
  639. ssh.AddCommand<GenerateSshConfigCommand>("export")
  640. .WithDescription("Generate an SSH config file.");
  641. });
  642. config.AddBranch("hosts", hosts => {
  643. hosts.SetDescription("Generate a hosts file from infrastructure.");
  644. hosts.AddCommand<GenerateHostsFileCommand>("export")
  645. .WithDescription("Generate a /etc/hosts compatible file.");
  646. });
  647. // ----------------------------
  648. // Graph / visualisation
  649. // ----------------------------
  650. config.AddBranch("graph", graph => {
  651. graph.SetDescription("Render inventory as graph diagrams.");
  652. graph.AddCommand<GraphTopologyCommand>("topology")
  653. .WithDescription("Emit a Mermaid flowchart of the physical topology (hardware + connections).");
  654. graph.AddCommand<GraphLogicalCommand>("logical")
  655. .WithDescription("Emit a Mermaid flowchart of services & systems grouped by subnet and host.");
  656. });
  657. // ----------------------------
  658. // Tags discovery
  659. // ----------------------------
  660. config.AddBranch("tags", tags => {
  661. tags.SetDescription("Discover tags across resources.");
  662. tags.AddCommand<TagsListCommand>("list")
  663. .WithDescription("List all tags in use with usage counts.");
  664. tags.AddCommand<TagsShowCommand>("show")
  665. .WithDescription("List resources carrying a specific tag.");
  666. });
  667. config.AddBranch("connections", connections => {
  668. connections.SetDescription("Manage physical or logical port connections.");
  669. connections.AddCommand<ConnectionAddCommand>("add")
  670. .WithDescription("Create a connection between two ports.");
  671. connections.AddCommand<ConnectionRemoveCommand>("remove")
  672. .WithDescription("Remove the connection from a specific port.");
  673. });
  674. });
  675. }
  676. private static int HandleException(Exception ex, ITypeResolver? arg2) {
  677. switch (ex) {
  678. case ValidationException ve:
  679. AnsiConsole.MarkupLine($"[yellow]Validation error:[/] {ve.Message}");
  680. return 2;
  681. case ConflictException ce:
  682. AnsiConsole.MarkupLine($"[red]Conflict:[/] {ce.Message}");
  683. return 3;
  684. case NotFoundException ne:
  685. AnsiConsole.MarkupLine($"[red]Not found:[/] {ne.Message}");
  686. return 4;
  687. case ConfigLoadException cle:
  688. AnsiConsole.MarkupLine($"[red]Config error:[/] {Markup.Escape(cle.Message)}");
  689. return 5;
  690. case CommandParseException pe:
  691. if (_showingHelp) return 1; // suppress errors during help lookup
  692. AnsiConsole.MarkupLine($"[red]Invalid command:[/] {pe.Message}");
  693. if (pe.Pretty != null) AnsiConsole.Write(pe.Pretty);
  694. ShowContextualHelp();
  695. return 1;
  696. case CommandRuntimeException re:
  697. if (_showingHelp) return 1;
  698. AnsiConsole.MarkupLine($"[red]Error:[/] {re.Message}");
  699. if (re.Pretty != null) AnsiConsole.Write(re.Pretty);
  700. ShowContextualHelp();
  701. return 1;
  702. default:
  703. AnsiConsole.MarkupLine("[red]Unexpected error occurred.[/]");
  704. AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
  705. return 99;
  706. }
  707. }
  708. private static void ShowContextualHelp() {
  709. if (_lastArgs == null || _app == null || _showingHelp) return;
  710. _showingHelp = true;
  711. try {
  712. // Extract command path (args before any --flags)
  713. var commandPath = _lastArgs.TakeWhile(a => !a.StartsWith("-")).ToList();
  714. // Try progressively shorter command paths until --help succeeds
  715. while (commandPath.Count > 0) {
  716. var helpArgs = commandPath.Append("--help").ToArray();
  717. AnsiConsole.WriteLine();
  718. var result = _app.Run(helpArgs);
  719. if (result == 0) return;
  720. commandPath.RemoveAt(commandPath.Count - 1);
  721. }
  722. }
  723. finally {
  724. _showingHelp = false;
  725. }
  726. }
  727. }