Initial commit (history squashed)
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build Test Deploy / authority-container (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / docs (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / deploy (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / build-test (push) Has been cancelled
				
			
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build Test Deploy / authority-container (push) Has been cancelled
				
			Build Test Deploy / docs (push) Has been cancelled
				
			Build Test Deploy / deploy (push) Has been cancelled
				
			Build Test Deploy / build-test (push) Has been cancelled
				
			Docs CI / lint-and-preview (push) Has been cancelled
				
			This commit is contained in:
		
							
								
								
									
										324
									
								
								src/StellaOps.Cli/Commands/CommandFactory.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								src/StellaOps.Cli/Commands/CommandFactory.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,324 @@ | ||||
| using System; | ||||
| using System.CommandLine; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using StellaOps.Cli.Configuration; | ||||
|  | ||||
| namespace StellaOps.Cli.Commands; | ||||
|  | ||||
| internal static class CommandFactory | ||||
| { | ||||
|     public static RootCommand Create(IServiceProvider services, StellaOpsCliOptions options, CancellationToken cancellationToken) | ||||
|     { | ||||
|         var verboseOption = new Option<bool>("--verbose", new[] { "-v" }) | ||||
|         { | ||||
|             Description = "Enable verbose logging output." | ||||
|         }; | ||||
|  | ||||
|         var root = new RootCommand("StellaOps command-line interface") | ||||
|         { | ||||
|             TreatUnmatchedTokensAsErrors = true | ||||
|         }; | ||||
|         root.Add(verboseOption); | ||||
|  | ||||
|         root.Add(BuildScannerCommand(services, verboseOption, cancellationToken)); | ||||
|         root.Add(BuildScanCommand(services, options, verboseOption, cancellationToken)); | ||||
|         root.Add(BuildDatabaseCommand(services, verboseOption, cancellationToken)); | ||||
|         root.Add(BuildAuthCommand(services, options, verboseOption, cancellationToken)); | ||||
|         root.Add(BuildConfigCommand(options)); | ||||
|  | ||||
|         return root; | ||||
|     } | ||||
|  | ||||
|     private static Command BuildScannerCommand(IServiceProvider services, Option<bool> verboseOption, CancellationToken cancellationToken) | ||||
|     { | ||||
|         var scanner = new Command("scanner", "Manage scanner artifacts and lifecycle."); | ||||
|  | ||||
|         var download = new Command("download", "Download the latest scanner bundle."); | ||||
|         var channelOption = new Option<string>("--channel", new[] { "-c" }) | ||||
|         { | ||||
|             Description = "Scanner channel (stable, beta, nightly)." | ||||
|         }; | ||||
|  | ||||
|         var outputOption = new Option<string?>("--output") | ||||
|         { | ||||
|             Description = "Optional output path for the downloaded bundle." | ||||
|         }; | ||||
|  | ||||
|         var overwriteOption = new Option<bool>("--overwrite") | ||||
|         { | ||||
|             Description = "Overwrite existing bundle if present." | ||||
|         }; | ||||
|  | ||||
|         var noInstallOption = new Option<bool>("--no-install") | ||||
|         { | ||||
|             Description = "Skip installing the scanner container after download." | ||||
|         }; | ||||
|  | ||||
|         download.Add(channelOption); | ||||
|         download.Add(outputOption); | ||||
|         download.Add(overwriteOption); | ||||
|         download.Add(noInstallOption); | ||||
|  | ||||
|         download.SetAction((parseResult, _) => | ||||
|         { | ||||
|             var channel = parseResult.GetValue(channelOption) ?? "stable"; | ||||
|             var output = parseResult.GetValue(outputOption); | ||||
|             var overwrite = parseResult.GetValue(overwriteOption); | ||||
|             var install = !parseResult.GetValue(noInstallOption); | ||||
|             var verbose = parseResult.GetValue(verboseOption); | ||||
|  | ||||
|             return CommandHandlers.HandleScannerDownloadAsync(services, channel, output, overwrite, install, verbose, cancellationToken); | ||||
|         }); | ||||
|  | ||||
|         scanner.Add(download); | ||||
|         return scanner; | ||||
|     } | ||||
|  | ||||
|     private static Command BuildScanCommand(IServiceProvider services, StellaOpsCliOptions options, Option<bool> verboseOption, CancellationToken cancellationToken) | ||||
|     { | ||||
|         var scan = new Command("scan", "Execute scanners and manage scan outputs."); | ||||
|  | ||||
|         var run = new Command("run", "Execute a scanner bundle with the configured runner."); | ||||
|         var runnerOption = new Option<string>("--runner") | ||||
|         { | ||||
|             Description = "Execution runtime (dotnet, self, docker)." | ||||
|         }; | ||||
|         var entryOption = new Option<string>("--entry") | ||||
|         { | ||||
|             Description = "Path to the scanner entrypoint or Docker image.", | ||||
|             Required = true | ||||
|         }; | ||||
|         var targetOption = new Option<string>("--target") | ||||
|         { | ||||
|             Description = "Directory to scan.", | ||||
|             Required = true | ||||
|         }; | ||||
|  | ||||
|         var argsArgument = new Argument<string[]>("scanner-args") | ||||
|         { | ||||
|             Arity = ArgumentArity.ZeroOrMore | ||||
|         }; | ||||
|  | ||||
|         run.Add(runnerOption); | ||||
|         run.Add(entryOption); | ||||
|         run.Add(targetOption); | ||||
|         run.Add(argsArgument); | ||||
|  | ||||
|         run.SetAction((parseResult, _) => | ||||
|         { | ||||
|             var runner = parseResult.GetValue(runnerOption) ?? options.DefaultRunner; | ||||
|             var entry = parseResult.GetValue(entryOption) ?? string.Empty; | ||||
|             var target = parseResult.GetValue(targetOption) ?? string.Empty; | ||||
|             var forwardedArgs = parseResult.GetValue(argsArgument) ?? Array.Empty<string>(); | ||||
|             var verbose = parseResult.GetValue(verboseOption); | ||||
|  | ||||
|             return CommandHandlers.HandleScannerRunAsync(services, runner, entry, target, forwardedArgs, verbose, cancellationToken); | ||||
|         }); | ||||
|  | ||||
|         var upload = new Command("upload", "Upload completed scan results to the backend."); | ||||
|         var fileOption = new Option<string>("--file") | ||||
|         { | ||||
|             Description = "Path to the scan result artifact.", | ||||
|             Required = true | ||||
|         }; | ||||
|         upload.Add(fileOption); | ||||
|         upload.SetAction((parseResult, _) => | ||||
|         { | ||||
|             var file = parseResult.GetValue(fileOption) ?? string.Empty; | ||||
|             var verbose = parseResult.GetValue(verboseOption); | ||||
|             return CommandHandlers.HandleScanUploadAsync(services, file, verbose, cancellationToken); | ||||
|         }); | ||||
|  | ||||
|         scan.Add(run); | ||||
|         scan.Add(upload); | ||||
|         return scan; | ||||
|     } | ||||
|  | ||||
|     private static Command BuildDatabaseCommand(IServiceProvider services, Option<bool> verboseOption, CancellationToken cancellationToken) | ||||
|     { | ||||
|         var db = new Command("db", "Trigger Feedser database operations via backend jobs."); | ||||
|  | ||||
|         var fetch = new Command("fetch", "Trigger connector fetch/parse/map stages."); | ||||
|         var sourceOption = new Option<string>("--source") | ||||
|         { | ||||
|             Description = "Connector source identifier (e.g. redhat, osv, vmware).", | ||||
|             Required = true | ||||
|         }; | ||||
|         var stageOption = new Option<string>("--stage") | ||||
|         { | ||||
|             Description = "Stage to trigger: fetch, parse, or map." | ||||
|         }; | ||||
|         var modeOption = new Option<string?>("--mode") | ||||
|         { | ||||
|             Description = "Optional connector-specific mode (init, resume, cursor)." | ||||
|         }; | ||||
|  | ||||
|         fetch.Add(sourceOption); | ||||
|         fetch.Add(stageOption); | ||||
|         fetch.Add(modeOption); | ||||
|         fetch.SetAction((parseResult, _) => | ||||
|         { | ||||
|             var source = parseResult.GetValue(sourceOption) ?? string.Empty; | ||||
|             var stage = parseResult.GetValue(stageOption) ?? "fetch"; | ||||
|             var mode = parseResult.GetValue(modeOption); | ||||
|             var verbose = parseResult.GetValue(verboseOption); | ||||
|  | ||||
|             return CommandHandlers.HandleConnectorJobAsync(services, source, stage, mode, verbose, cancellationToken); | ||||
|         }); | ||||
|  | ||||
|         var merge = new Command("merge", "Run canonical merge reconciliation."); | ||||
|         merge.SetAction((parseResult, _) => | ||||
|         { | ||||
|             var verbose = parseResult.GetValue(verboseOption); | ||||
|             return CommandHandlers.HandleMergeJobAsync(services, verbose, cancellationToken); | ||||
|         }); | ||||
|  | ||||
|         var export = new Command("export", "Run Feedser export jobs."); | ||||
|         var formatOption = new Option<string>("--format") | ||||
|         { | ||||
|             Description = "Export format: json or trivy-db." | ||||
|         }; | ||||
|         var deltaOption = new Option<bool>("--delta") | ||||
|         { | ||||
|             Description = "Request a delta export when supported." | ||||
|         }; | ||||
|         var publishFullOption = new Option<bool?>("--publish-full") | ||||
|         { | ||||
|             Description = "Override whether full exports push to ORAS (true/false)." | ||||
|         }; | ||||
|         var publishDeltaOption = new Option<bool?>("--publish-delta") | ||||
|         { | ||||
|             Description = "Override whether delta exports push to ORAS (true/false)." | ||||
|         }; | ||||
|         var includeFullOption = new Option<bool?>("--bundle-full") | ||||
|         { | ||||
|             Description = "Override whether offline bundles include full exports (true/false)." | ||||
|         }; | ||||
|         var includeDeltaOption = new Option<bool?>("--bundle-delta") | ||||
|         { | ||||
|             Description = "Override whether offline bundles include delta exports (true/false)." | ||||
|         }; | ||||
|  | ||||
|         export.Add(formatOption); | ||||
|         export.Add(deltaOption); | ||||
|         export.Add(publishFullOption); | ||||
|         export.Add(publishDeltaOption); | ||||
|         export.Add(includeFullOption); | ||||
|         export.Add(includeDeltaOption); | ||||
|         export.SetAction((parseResult, _) => | ||||
|         { | ||||
|             var format = parseResult.GetValue(formatOption) ?? "json"; | ||||
|             var delta = parseResult.GetValue(deltaOption); | ||||
|             var publishFull = parseResult.GetValue(publishFullOption); | ||||
|             var publishDelta = parseResult.GetValue(publishDeltaOption); | ||||
|             var includeFull = parseResult.GetValue(includeFullOption); | ||||
|             var includeDelta = parseResult.GetValue(includeDeltaOption); | ||||
|             var verbose = parseResult.GetValue(verboseOption); | ||||
|             return CommandHandlers.HandleExportJobAsync(services, format, delta, publishFull, publishDelta, includeFull, includeDelta, verbose, cancellationToken); | ||||
|         }); | ||||
|  | ||||
|         db.Add(fetch); | ||||
|         db.Add(merge); | ||||
|         db.Add(export); | ||||
|         return db; | ||||
|     } | ||||
|  | ||||
|     private static Command BuildAuthCommand(IServiceProvider services, StellaOpsCliOptions options, Option<bool> verboseOption, CancellationToken cancellationToken) | ||||
|     { | ||||
|         var auth = new Command("auth", "Manage authentication with StellaOps Authority."); | ||||
|  | ||||
|         var login = new Command("login", "Acquire and cache access tokens using the configured credentials."); | ||||
|         var forceOption = new Option<bool>("--force") | ||||
|         { | ||||
|             Description = "Ignore existing cached tokens and force re-authentication." | ||||
|         }; | ||||
|         login.Add(forceOption); | ||||
|         login.SetAction((parseResult, _) => | ||||
|         { | ||||
|             var verbose = parseResult.GetValue(verboseOption); | ||||
|             var force = parseResult.GetValue(forceOption); | ||||
|             return CommandHandlers.HandleAuthLoginAsync(services, options, verbose, force, cancellationToken); | ||||
|         }); | ||||
|  | ||||
|         var logout = new Command("logout", "Remove cached tokens for the current credentials."); | ||||
|         logout.SetAction((parseResult, _) => | ||||
|         { | ||||
|             var verbose = parseResult.GetValue(verboseOption); | ||||
|             return CommandHandlers.HandleAuthLogoutAsync(services, options, verbose, cancellationToken); | ||||
|         }); | ||||
|  | ||||
|         var status = new Command("status", "Display cached token status."); | ||||
|         status.SetAction((parseResult, _) => | ||||
|         { | ||||
|             var verbose = parseResult.GetValue(verboseOption); | ||||
|             return CommandHandlers.HandleAuthStatusAsync(services, options, verbose, cancellationToken); | ||||
|         }); | ||||
|  | ||||
|         var whoami = new Command("whoami", "Display cached token claims (subject, scopes, expiry)."); | ||||
|         whoami.SetAction((parseResult, _) => | ||||
|         { | ||||
|             var verbose = parseResult.GetValue(verboseOption); | ||||
|             return CommandHandlers.HandleAuthWhoAmIAsync(services, options, verbose, cancellationToken); | ||||
|         }); | ||||
|  | ||||
|         auth.Add(login); | ||||
|         auth.Add(logout); | ||||
|         auth.Add(status); | ||||
|         auth.Add(whoami); | ||||
|         return auth; | ||||
|     } | ||||
|  | ||||
|     private static Command BuildConfigCommand(StellaOpsCliOptions options) | ||||
|     { | ||||
|         var config = new Command("config", "Inspect CLI configuration state."); | ||||
|         var show = new Command("show", "Display resolved configuration values."); | ||||
|  | ||||
|         show.SetAction((_, _) => | ||||
|         { | ||||
|             var authority = options.Authority ?? new StellaOpsCliAuthorityOptions(); | ||||
|             var lines = new[] | ||||
|             { | ||||
|                 $"Backend URL: {MaskIfEmpty(options.BackendUrl)}", | ||||
|                 $"API Key: {DescribeSecret(options.ApiKey)}", | ||||
|                 $"Scanner Cache: {options.ScannerCacheDirectory}", | ||||
|                 $"Results Directory: {options.ResultsDirectory}", | ||||
|                 $"Default Runner: {options.DefaultRunner}", | ||||
|                 $"Authority URL: {MaskIfEmpty(authority.Url)}", | ||||
|                 $"Authority Client ID: {MaskIfEmpty(authority.ClientId)}", | ||||
|                 $"Authority Client Secret: {DescribeSecret(authority.ClientSecret ?? string.Empty)}", | ||||
|                 $"Authority Username: {MaskIfEmpty(authority.Username)}", | ||||
|                 $"Authority Password: {DescribeSecret(authority.Password ?? string.Empty)}", | ||||
|                 $"Authority Scope: {MaskIfEmpty(authority.Scope)}", | ||||
|                 $"Authority Token Cache: {MaskIfEmpty(authority.TokenCacheDirectory ?? string.Empty)}" | ||||
|             }; | ||||
|  | ||||
|             foreach (var line in lines) | ||||
|             { | ||||
|                 Console.WriteLine(line); | ||||
|             } | ||||
|  | ||||
|             return Task.CompletedTask; | ||||
|         }); | ||||
|  | ||||
|         config.Add(show); | ||||
|         return config; | ||||
|     } | ||||
|  | ||||
|     private static string MaskIfEmpty(string value) | ||||
|         => string.IsNullOrWhiteSpace(value) ? "<not configured>" : value; | ||||
|  | ||||
|     private static string DescribeSecret(string value) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(value)) | ||||
|         { | ||||
|             return "<not configured>"; | ||||
|         } | ||||
|  | ||||
|         return value.Length switch | ||||
|         { | ||||
|             <= 4 => "****", | ||||
|             _ => $"{value[..2]}***{value[^2..]}" | ||||
|         }; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user