Add unit tests for Router configuration and transport layers
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

- Implemented tests for RouterConfig, RoutingOptions, StaticInstanceConfig, and RouterConfigOptions to ensure default values are set correctly.
- Added tests for RouterConfigProvider to validate configurations and ensure defaults are returned when no file is specified.
- Created tests for ConfigValidationResult to check success and error scenarios.
- Developed tests for ServiceCollectionExtensions to verify service registration for RouterConfig.
- Introduced UdpTransportTests to validate serialization, connection, request-response, and error handling in UDP transport.
- Added scripts for signing authority gaps and hashing DevPortal SDK snippets.
This commit is contained in:
StellaOps Bot
2025-12-05 08:01:47 +02:00
parent 635c70e828
commit 6a299d231f
294 changed files with 28434 additions and 1329 deletions

View File

@@ -74,6 +74,7 @@ internal static class CommandFactory
root.Add(BuildApiCommand(services, verboseOption, cancellationToken));
root.Add(BuildSdkCommand(services, verboseOption, cancellationToken));
root.Add(BuildMirrorCommand(services, verboseOption, cancellationToken));
root.Add(BuildAirgapCommand(services, verboseOption, cancellationToken));
var pluginLogger = loggerFactory.CreateLogger<CliCommandModuleLoader>();
var pluginLoader = new CliCommandModuleLoader(services, options, pluginLogger);
@@ -4207,12 +4208,22 @@ internal static class CommandFactory
{
Description = "Output path for verification report."
};
var verifyFormatOption = new Option<string?>("--format", new[] { "-f" })
{
Description = "Output format: table (default), json."
};
var verifyExplainOption = new Option<bool>("--explain")
{
Description = "Include detailed explanations for each verification check."
};
verify.Add(envelopeOption);
verify.Add(policyOption);
verify.Add(rootOption);
verify.Add(checkpointOption);
verify.Add(verifyOutputOption);
verify.Add(verifyFormatOption);
verify.Add(verifyExplainOption);
verify.SetAction((parseResult, _) =>
{
@@ -4221,44 +4232,70 @@ internal static class CommandFactory
var root = parseResult.GetValue(rootOption);
var checkpoint = parseResult.GetValue(checkpointOption);
var output = parseResult.GetValue(verifyOutputOption);
var format = parseResult.GetValue(verifyFormatOption) ?? "table";
var explain = parseResult.GetValue(verifyExplainOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleAttestVerifyAsync(services, envelope, policy, root, checkpoint, output, verbose, cancellationToken);
return CommandHandlers.HandleAttestVerifyAsync(services, envelope, policy, root, checkpoint, output, format, explain, verbose, cancellationToken);
});
// attest list
var list = new Command("list", "List attestations from the backend.");
var tenantOption = new Option<string?>("--tenant")
// attest list (CLI-ATTEST-74-001)
var list = new Command("list", "List attestations from local storage or backend.");
var listTenantOption = new Option<string?>("--tenant")
{
Description = "Tenant identifier to filter by."
Description = "Filter by tenant identifier."
};
var issuerOption = new Option<string?>("--issuer")
var listIssuerOption = new Option<string?>("--issuer")
{
Description = "Issuer identifier to filter by."
Description = "Filter by issuer identifier."
};
var formatOption = new Option<string?>("--format", new[] { "-f" })
var listSubjectOption = new Option<string?>("--subject", new[] { "-s" })
{
Description = "Output format (table, json)."
Description = "Filter by subject (e.g., image digest, package PURL)."
};
var limitOption = new Option<int?>("--limit", new[] { "-n" })
var listTypeOption = new Option<string?>("--type", new[] { "-t" })
{
Description = "Maximum number of results to return."
Description = "Filter by predicate type URI."
};
var listScopeOption = new Option<string?>("--scope")
{
Description = "Filter by scope (local, remote, all). Default: all."
};
var listFormatOption = new Option<string?>("--format", new[] { "-f" })
{
Description = "Output format (table, json). Default: table."
};
var listLimitOption = new Option<int?>("--limit", new[] { "-n" })
{
Description = "Maximum number of results to return. Default: 50."
};
var listOffsetOption = new Option<int?>("--offset")
{
Description = "Number of results to skip (for pagination). Default: 0."
};
list.Add(tenantOption);
list.Add(issuerOption);
list.Add(formatOption);
list.Add(limitOption);
list.Add(listTenantOption);
list.Add(listIssuerOption);
list.Add(listSubjectOption);
list.Add(listTypeOption);
list.Add(listScopeOption);
list.Add(listFormatOption);
list.Add(listLimitOption);
list.Add(listOffsetOption);
list.SetAction((parseResult, _) =>
{
var tenant = parseResult.GetValue(tenantOption);
var issuer = parseResult.GetValue(issuerOption);
var format = parseResult.GetValue(formatOption) ?? "table";
var limit = parseResult.GetValue(limitOption);
var tenant = parseResult.GetValue(listTenantOption);
var issuer = parseResult.GetValue(listIssuerOption);
var subject = parseResult.GetValue(listSubjectOption);
var type = parseResult.GetValue(listTypeOption);
var scope = parseResult.GetValue(listScopeOption) ?? "all";
var format = parseResult.GetValue(listFormatOption) ?? "table";
var limit = parseResult.GetValue(listLimitOption);
var offset = parseResult.GetValue(listOffsetOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleAttestListAsync(services, tenant, issuer, format, limit, verbose, cancellationToken);
return CommandHandlers.HandleAttestListAsync(services, tenant, issuer, subject, type, scope, format, limit, offset, verbose, cancellationToken);
});
// attest show
@@ -4291,9 +4328,398 @@ internal static class CommandFactory
return CommandHandlers.HandleAttestShowAsync(services, id, output, includeProof, verbose, cancellationToken);
});
// attest sign (CLI-ATTEST-73-001)
var sign = new Command("sign", "Create and sign a DSSE attestation envelope.");
var predicateFileOption = new Option<string>("--predicate", new[] { "-p" })
{
Description = "Path to the predicate JSON file.",
Required = true
};
var predicateTypeOption = new Option<string>("--predicate-type")
{
Description = "Predicate type URI (e.g., https://slsa.dev/provenance/v1).",
Required = true
};
var subjectNameOption = new Option<string>("--subject")
{
Description = "Subject name or URI to attest.",
Required = true
};
var subjectDigestOption = new Option<string>("--digest")
{
Description = "Subject digest in format algorithm:hex (e.g., sha256:abc123...).",
Required = true
};
var signKeyOption = new Option<string?>("--key", new[] { "-k" })
{
Description = "Key identifier or path for signing."
};
var keylessOption = new Option<bool>("--keyless")
{
Description = "Use keyless (OIDC) signing via Sigstore Fulcio."
};
var transparencyLogOption = new Option<bool>("--rekor")
{
Description = "Submit attestation to Rekor transparency log (default: false)."
};
var noRekorOption = new Option<bool>("--no-rekor")
{
Description = "Explicitly skip Rekor submission."
};
var signOutputOption = new Option<string?>("--output", new[] { "-o" })
{
Description = "Output path for the signed DSSE envelope JSON."
};
var signFormatOption = new Option<string?>("--format", new[] { "-f" })
{
Description = "Output format: dsse (default), sigstore-bundle."
};
sign.Add(predicateFileOption);
sign.Add(predicateTypeOption);
sign.Add(subjectNameOption);
sign.Add(subjectDigestOption);
sign.Add(signKeyOption);
sign.Add(keylessOption);
sign.Add(transparencyLogOption);
sign.Add(noRekorOption);
sign.Add(signOutputOption);
sign.Add(signFormatOption);
sign.SetAction((parseResult, _) =>
{
var predicatePath = parseResult.GetValue(predicateFileOption)!;
var predicateType = parseResult.GetValue(predicateTypeOption)!;
var subjectName = parseResult.GetValue(subjectNameOption)!;
var digest = parseResult.GetValue(subjectDigestOption)!;
var keyId = parseResult.GetValue(signKeyOption);
var keyless = parseResult.GetValue(keylessOption);
var useRekor = parseResult.GetValue(transparencyLogOption);
var noRekor = parseResult.GetValue(noRekorOption);
var output = parseResult.GetValue(signOutputOption);
var format = parseResult.GetValue(signFormatOption) ?? "dsse";
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleAttestSignAsync(
services,
predicatePath,
predicateType,
subjectName,
digest,
keyId,
keyless,
useRekor && !noRekor,
output,
format,
verbose,
cancellationToken);
});
// attest fetch (CLI-ATTEST-74-002)
var fetch = new Command("fetch", "Download attestation envelopes and payloads to disk.");
var fetchIdOption = new Option<string?>("--id")
{
Description = "Attestation ID to fetch."
};
var fetchSubjectOption = new Option<string?>("--subject", new[] { "-s" })
{
Description = "Subject filter (e.g., image digest, package PURL)."
};
var fetchTypeOption = new Option<string?>("--type", new[] { "-t" })
{
Description = "Predicate type filter."
};
var fetchOutputDirOption = new Option<string>("--output-dir", new[] { "-o" })
{
Description = "Output directory for downloaded files.",
Required = true
};
var fetchIncludeOption = new Option<string?>("--include")
{
Description = "What to download: envelope, payload, both (default: both)."
};
var fetchScopeOption = new Option<string?>("--scope")
{
Description = "Source scope: local, remote, all (default: all)."
};
var fetchFormatOption = new Option<string?>("--format", new[] { "-f" })
{
Description = "Output format for payloads: json (default), raw."
};
var fetchOverwriteOption = new Option<bool>("--overwrite")
{
Description = "Overwrite existing files."
};
fetch.Add(fetchIdOption);
fetch.Add(fetchSubjectOption);
fetch.Add(fetchTypeOption);
fetch.Add(fetchOutputDirOption);
fetch.Add(fetchIncludeOption);
fetch.Add(fetchScopeOption);
fetch.Add(fetchFormatOption);
fetch.Add(fetchOverwriteOption);
fetch.SetAction((parseResult, _) =>
{
var id = parseResult.GetValue(fetchIdOption);
var subject = parseResult.GetValue(fetchSubjectOption);
var type = parseResult.GetValue(fetchTypeOption);
var outputDir = parseResult.GetValue(fetchOutputDirOption)!;
var include = parseResult.GetValue(fetchIncludeOption) ?? "both";
var scope = parseResult.GetValue(fetchScopeOption) ?? "all";
var format = parseResult.GetValue(fetchFormatOption) ?? "json";
var overwrite = parseResult.GetValue(fetchOverwriteOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleAttestFetchAsync(
services,
id,
subject,
type,
outputDir,
include,
scope,
format,
overwrite,
verbose,
cancellationToken);
});
// attest key (CLI-ATTEST-75-001)
var key = new Command("key", "Manage attestation signing keys.");
// attest key create
var keyCreate = new Command("create", "Create a new signing key for attestations.");
var keyNameOption = new Option<string>("--name", new[] { "-n" })
{
Description = "Key identifier/name.",
Required = true
};
var keyAlgorithmOption = new Option<string?>("--algorithm", new[] { "-a" })
{
Description = "Key algorithm: ECDSA-P256 (default), ECDSA-P384."
};
var keyPasswordOption = new Option<string?>("--password", new[] { "-p" })
{
Description = "Password to protect the key (required for file-based keys)."
};
var keyOutputOption = new Option<string?>("--output", new[] { "-o" })
{
Description = "Output path for the key directory (default: ~/.stellaops/keys)."
};
var keyFormatOption = new Option<string?>("--format", new[] { "-f" })
{
Description = "Output format: table (default), json."
};
var keyExportPublicOption = new Option<bool>("--export-public")
{
Description = "Export public key to file alongside key creation."
};
keyCreate.Add(keyNameOption);
keyCreate.Add(keyAlgorithmOption);
keyCreate.Add(keyPasswordOption);
keyCreate.Add(keyOutputOption);
keyCreate.Add(keyFormatOption);
keyCreate.Add(keyExportPublicOption);
keyCreate.SetAction((parseResult, _) =>
{
var name = parseResult.GetValue(keyNameOption)!;
var algorithm = parseResult.GetValue(keyAlgorithmOption) ?? "ECDSA-P256";
var password = parseResult.GetValue(keyPasswordOption);
var output = parseResult.GetValue(keyOutputOption);
var format = parseResult.GetValue(keyFormatOption) ?? "table";
var exportPublic = parseResult.GetValue(keyExportPublicOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleAttestKeyCreateAsync(
services,
name,
algorithm,
password,
output,
format,
exportPublic,
verbose,
cancellationToken);
});
key.Add(keyCreate);
// attest bundle (CLI-ATTEST-75-002)
var bundle = new Command("bundle", "Build and verify attestation bundles.");
// attest bundle build
var bundleBuild = new Command("build", "Build an audit bundle from artifacts (attestations, SBOMs, VEX, scans).");
var bundleSubjectNameOption = new Option<string>("--subject-name", new[] { "-s" })
{
Description = "Primary subject name (e.g., image reference).",
Required = true
};
var bundleSubjectDigestOption = new Option<string>("--subject-digest", new[] { "-d" })
{
Description = "Subject digest in algorithm:hex format (e.g., sha256:abc123...).",
Required = true
};
var bundleSubjectTypeOption = new Option<string?>("--subject-type")
{
Description = "Subject type: IMAGE (default), REPO, SBOM, OTHER."
};
var bundleInputDirOption = new Option<string>("--input", new[] { "-i" })
{
Description = "Input directory containing artifacts to bundle.",
Required = true
};
var bundleOutputOption = new Option<string>("--output", new[] { "-o" })
{
Description = "Output path for the bundle (directory or .tar.gz file).",
Required = true
};
var bundleFromOption = new Option<string?>("--from")
{
Description = "Start of time window for artifacts (ISO-8601)."
};
var bundleToOption = new Option<string?>("--to")
{
Description = "End of time window for artifacts (ISO-8601)."
};
var bundleIncludeOption = new Option<string?>("--include")
{
Description = "Artifact types to include: attestations,sboms,vex,scans,policy,all (default: all)."
};
var bundleCompressOption = new Option<bool>("--compress")
{
Description = "Compress output as tar.gz."
};
var bundleCreatorIdOption = new Option<string?>("--creator-id")
{
Description = "Creator user ID (default: current user)."
};
var bundleCreatorNameOption = new Option<string?>("--creator-name")
{
Description = "Creator display name (default: current user)."
};
var bundleFormatOption = new Option<string?>("--format", new[] { "-f" })
{
Description = "Output format: table (default), json."
};
bundleBuild.Add(bundleSubjectNameOption);
bundleBuild.Add(bundleSubjectDigestOption);
bundleBuild.Add(bundleSubjectTypeOption);
bundleBuild.Add(bundleInputDirOption);
bundleBuild.Add(bundleOutputOption);
bundleBuild.Add(bundleFromOption);
bundleBuild.Add(bundleToOption);
bundleBuild.Add(bundleIncludeOption);
bundleBuild.Add(bundleCompressOption);
bundleBuild.Add(bundleCreatorIdOption);
bundleBuild.Add(bundleCreatorNameOption);
bundleBuild.Add(bundleFormatOption);
bundleBuild.SetAction((parseResult, _) =>
{
var subjectName = parseResult.GetValue(bundleSubjectNameOption)!;
var subjectDigest = parseResult.GetValue(bundleSubjectDigestOption)!;
var subjectType = parseResult.GetValue(bundleSubjectTypeOption) ?? "IMAGE";
var inputDir = parseResult.GetValue(bundleInputDirOption)!;
var output = parseResult.GetValue(bundleOutputOption)!;
var from = parseResult.GetValue(bundleFromOption);
var to = parseResult.GetValue(bundleToOption);
var include = parseResult.GetValue(bundleIncludeOption) ?? "all";
var compress = parseResult.GetValue(bundleCompressOption);
var creatorId = parseResult.GetValue(bundleCreatorIdOption);
var creatorName = parseResult.GetValue(bundleCreatorNameOption);
var format = parseResult.GetValue(bundleFormatOption) ?? "table";
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleAttestBundleBuildAsync(
services,
subjectName,
subjectDigest,
subjectType,
inputDir,
output,
from,
to,
include,
compress,
creatorId,
creatorName,
format,
verbose,
cancellationToken);
});
// attest bundle verify
var bundleVerify = new Command("verify", "Verify an attestation bundle's integrity and signatures.");
var bundleVerifyInputOption = new Option<string>("--input", new[] { "-i" })
{
Description = "Input bundle path (directory or .tar.gz file).",
Required = true
};
var bundleVerifyPolicyOption = new Option<string?>("--policy")
{
Description = "Policy file for attestation verification (JSON with requiredPredicateTypes, minimumSignatures, etc.)."
};
var bundleVerifyRootOption = new Option<string?>("--root")
{
Description = "Trust root file (PEM certificate or public key) for signature verification."
};
var bundleVerifyOutputOption = new Option<string?>("--output", new[] { "-o" })
{
Description = "Write verification report to file (JSON format)."
};
var bundleVerifyFormatOption = new Option<string?>("--format", new[] { "-f" })
{
Description = "Output format: table (default), json."
};
var bundleVerifyStrictOption = new Option<bool>("--strict")
{
Description = "Treat warnings as errors (exit code 1 on any issue)."
};
bundleVerify.Add(bundleVerifyInputOption);
bundleVerify.Add(bundleVerifyPolicyOption);
bundleVerify.Add(bundleVerifyRootOption);
bundleVerify.Add(bundleVerifyOutputOption);
bundleVerify.Add(bundleVerifyFormatOption);
bundleVerify.Add(bundleVerifyStrictOption);
bundleVerify.SetAction((parseResult, _) =>
{
var input = parseResult.GetValue(bundleVerifyInputOption)!;
var policy = parseResult.GetValue(bundleVerifyPolicyOption);
var root = parseResult.GetValue(bundleVerifyRootOption);
var output = parseResult.GetValue(bundleVerifyOutputOption);
var format = parseResult.GetValue(bundleVerifyFormatOption) ?? "table";
var strict = parseResult.GetValue(bundleVerifyStrictOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleAttestBundleVerifyAsync(
services,
input,
policy,
root,
output,
format,
strict,
verbose,
cancellationToken);
});
bundle.Add(bundleBuild);
bundle.Add(bundleVerify);
attest.Add(sign);
attest.Add(verify);
attest.Add(list);
attest.Add(show);
attest.Add(fetch);
attest.Add(key);
attest.Add(bundle);
return attest;
}
@@ -9835,4 +10261,238 @@ internal static class CommandFactory
return mirror;
}
private static Command BuildAirgapCommand(IServiceProvider services, Option<bool> verboseOption, CancellationToken cancellationToken)
{
var airgap = new Command("airgap", "Manage air-gapped environment operations.");
// airgap import (CLI-AIRGAP-57-001)
var import = new Command("import", "Import an air-gap mirror bundle into the local data store.");
var bundlePathOption = new Option<string>("--bundle", new[] { "-b" })
{
Description = "Path to the bundle directory (contains manifest.json and artifacts).",
Required = true
};
var importTenantOption = new Option<string?>("--tenant")
{
Description = "Import data under a specific tenant scope."
};
var globalOption = new Option<bool>("--global")
{
Description = "Import data to the global scope (requires elevated permissions)."
};
var dryRunOption = new Option<bool>("--dry-run")
{
Description = "Preview the import without making changes."
};
var forceOption = new Option<bool>("--force")
{
Description = "Force import even if checksums have been verified before."
};
var verifyOnlyOption = new Option<bool>("--verify-only")
{
Description = "Verify bundle integrity without importing."
};
var importJsonOption = new Option<bool>("--json")
{
Description = "Output results in JSON format."
};
import.Add(bundlePathOption);
import.Add(importTenantOption);
import.Add(globalOption);
import.Add(dryRunOption);
import.Add(forceOption);
import.Add(verifyOnlyOption);
import.Add(importJsonOption);
import.SetAction((parseResult, _) =>
{
var bundlePath = parseResult.GetValue(bundlePathOption)!;
var tenant = parseResult.GetValue(importTenantOption);
var global = parseResult.GetValue(globalOption);
var dryRun = parseResult.GetValue(dryRunOption);
var force = parseResult.GetValue(forceOption);
var verifyOnly = parseResult.GetValue(verifyOnlyOption);
var json = parseResult.GetValue(importJsonOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleAirgapImportAsync(
services,
bundlePath,
tenant,
global,
dryRun,
force,
verifyOnly,
json,
verbose,
cancellationToken);
});
airgap.Add(import);
// airgap seal (CLI-AIRGAP-57-002)
var seal = new Command("seal", "Seal the environment for air-gapped operation.");
var sealConfigDirOption = new Option<string?>("--config-dir", new[] { "-c" })
{
Description = "Path to the configuration directory (defaults to ~/.stellaops)."
};
var sealVerifyOption = new Option<bool>("--verify")
{
Description = "Verify imported bundles before sealing."
};
var sealForceOption = new Option<bool>("--force")
{
Description = "Force seal even if verification warnings exist."
};
var sealDryRunOption = new Option<bool>("--dry-run")
{
Description = "Preview the seal operation without making changes."
};
var sealJsonOption = new Option<bool>("--json")
{
Description = "Output results in JSON format."
};
var sealReasonOption = new Option<string?>("--reason")
{
Description = "Reason for sealing (recorded in audit log)."
};
seal.Add(sealConfigDirOption);
seal.Add(sealVerifyOption);
seal.Add(sealForceOption);
seal.Add(sealDryRunOption);
seal.Add(sealJsonOption);
seal.Add(sealReasonOption);
seal.SetAction((parseResult, _) =>
{
var configDir = parseResult.GetValue(sealConfigDirOption);
var verify = parseResult.GetValue(sealVerifyOption);
var force = parseResult.GetValue(sealForceOption);
var dryRun = parseResult.GetValue(sealDryRunOption);
var json = parseResult.GetValue(sealJsonOption);
var reason = parseResult.GetValue(sealReasonOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleAirgapSealAsync(
services,
configDir,
verify,
force,
dryRun,
json,
reason,
verbose,
cancellationToken);
});
airgap.Add(seal);
// airgap export-evidence (CLI-AIRGAP-58-001)
var exportEvidence = new Command("export-evidence", "Export portable evidence packages for audit and compliance.");
var evidenceOutputOption = new Option<string>("--output", new[] { "-o" })
{
Description = "Output directory for the evidence package.",
Required = true
};
var evidenceIncludeOption = new Option<string[]>("--include", new[] { "-i" })
{
Description = "Evidence types to include: attestations, sboms, scans, vex, all (default: all).",
AllowMultipleArgumentsPerToken = true
};
var evidenceFromOption = new Option<DateTimeOffset?>("--from")
{
Description = "Include evidence from this date (UTC, ISO-8601)."
};
var evidenceToOption = new Option<DateTimeOffset?>("--to")
{
Description = "Include evidence up to this date (UTC, ISO-8601)."
};
var evidenceTenantOption = new Option<string?>("--tenant")
{
Description = "Export evidence for a specific tenant."
};
var evidenceSubjectOption = new Option<string?>("--subject")
{
Description = "Filter evidence by subject (e.g., image digest, package PURL)."
};
var evidenceCompressOption = new Option<bool>("--compress")
{
Description = "Compress the output package as a .tar.gz archive."
};
var evidenceJsonOption = new Option<bool>("--json")
{
Description = "Output results in JSON format."
};
var evidenceVerifyOption = new Option<bool>("--verify")
{
Description = "Verify evidence signatures before export."
};
exportEvidence.Add(evidenceOutputOption);
exportEvidence.Add(evidenceIncludeOption);
exportEvidence.Add(evidenceFromOption);
exportEvidence.Add(evidenceToOption);
exportEvidence.Add(evidenceTenantOption);
exportEvidence.Add(evidenceSubjectOption);
exportEvidence.Add(evidenceCompressOption);
exportEvidence.Add(evidenceJsonOption);
exportEvidence.Add(evidenceVerifyOption);
exportEvidence.SetAction((parseResult, _) =>
{
var output = parseResult.GetValue(evidenceOutputOption)!;
var include = parseResult.GetValue(evidenceIncludeOption) ?? Array.Empty<string>();
var from = parseResult.GetValue(evidenceFromOption);
var to = parseResult.GetValue(evidenceToOption);
var tenant = parseResult.GetValue(evidenceTenantOption);
var subject = parseResult.GetValue(evidenceSubjectOption);
var compress = parseResult.GetValue(evidenceCompressOption);
var json = parseResult.GetValue(evidenceJsonOption);
var verify = parseResult.GetValue(evidenceVerifyOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleAirgapExportEvidenceAsync(
services,
output,
include,
from,
to,
tenant,
subject,
compress,
json,
verify,
verbose,
cancellationToken);
});
airgap.Add(exportEvidence);
return airgap;
}
}