Private/Models.ps1
|
# Copyright (c) 2026 Broadcom. All Rights Reserved. # Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc. # and/or its subsidiaries. # # ============================================================================= # # SOFTWARE LICENSE AGREEMENT # # Copyright (c) CA, Inc. All rights reserved. # # You are hereby granted a non-exclusive, worldwide, royalty-free license # under CA, Inc.'s copyrights to use, copy, modify, and distribute this # software in source code or binary form for use in connection with CA, Inc. # products. # # This copyright notice shall be included in all copies or substantial # portions of the software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # ============================================================================= #region Models function New-VcfCheckResult { <# .SYNOPSIS Builds a standardized result object for a single precheck. .DESCRIPTION Every Test-Vcf<CheckId> function returns one of these. Returns a PSCustomObject carrying the check metadata and result status, including a Skipped status (for scenarios not applicable to the environment), a Blocking flag (for upgrade-blocking conditions), and timing fields. .PARAMETER CheckId The check identifier (e.g. "sddc_lock_table"), matching entries in Data/CheckCatalog.json. .PARAMETER Area Product area: Aria Suite, ESX, NSX, SDDC Manager, vCenter, vSAN, Tanzu, or Sample. .PARAMETER DisplayName Human-readable title shown in the report. .PARAMETER Status Pass, Warning, Fail, Error, or Skipped. .PARAMETER Blocking True if a Fail status is known to hard-block a VCF upgrade. .PARAMETER Informational True if this check always reports Pass unless the check itself fails to run - it has no real fail criteria of its own, it just surfaces data (inventory, versions, config dumps) for the reader to interpret. Distinct from Blocking: a check can have real fail criteria (able to Warn/Fail on evaluated data) without being a hard upgrade blocker. .PARAMETER TargetComponent The FQDN/VM/host the check actually ran against. .PARAMETER Destination The external location the check's subject reports to or depends on, when applicable (e.g. an NSX Manager backup target as "server:port/directoryPath"). Left blank for checks with no such destination. .PARAMETER Detail Evidence/result text explaining the status. .PARAMETER SkipReasonTag Short bracketed reason shown on the summary line in place of Detail when Status is Skipped (e.g. "Not vSAN Stretched Cluster", "HCX not installed") - Detail's full sentence is still shown in the expanded card. Ignored for every other Status. .PARAMETER ValidationCriteria Plain-language statement of what "Pass" means for this check. .PARAMETER Remediation Guidance for resolving a Warning/Fail status. .PARAMETER StartedAt UTC timestamp when the check began. .PARAMETER CompletedAt UTC timestamp when the check finished. Used with StartedAt to compute DurationMs. .PARAMETER Exception Exception message, populated only when Status is Error. Callers must ensure this text never contains a plaintext secret. .PARAMETER Domain VCF domain name this result pertains to. Left blank by checks that only ever target a single fleet-wide/management-only component (SDDC Manager, Aria Suite) - Invoke-VcfCheck backfills a blank Domain to the real Management domain name after the check returns, unless the check set -Component instead (see below). .PARAMETER DomainType VCF domain type ("MANAGEMENT" or "VI") matching Domain. Backfilled the same way as Domain when left blank. .PARAMETER Component Fleet-wide component name (e.g. "Aria Operations") for a check that has no real VCF domain to report - it is not scoped to any domain, so backfilling a Domain onto it (as happens for other fleet-wide checks like SDDC Manager) would misleadingly imply one. Rendered as a "Component: <value>" pill in place of the Domain pill. Setting this suppresses Invoke-VcfCheck's Domain/DomainType backfill for this result. .PARAMETER Rows Structured per-object rows rendered as a single flat table in the JSON/HTML report. Every row is expected to share one schema - see Format-VcfCheckHtmlRowsTable. .PARAMETER HostDetails Structured per-host hardware/inventory data (e.g. esxi_hardware_details), rendered as one collapsible section per host in the HTML report instead of Rows' single flat table - see Format-VcfCheckHtmlHostDetailCard. Unrelated to Rows; a check uses whichever shape fits its data, not both. .PARAMETER HostDetailsLabel Section heading for HostDetails in the HTML report. Defaults to "Hosts"; checks whose HostDetails entries are not ESX hosts (e.g. NSX transport nodes) should override this so the report does not mislabel the entities it lists. .OUTPUTS [PSCustomObject] with PSTypeName 'VcfCheck.Result'. .EXAMPLE New-VcfCheckResult -CheckId 'sddc_lock_table' -Area 'SddcManager' ` -DisplayName 'SDDC Manager Platform Lock Table' -Status Fail -Blocking ` -TargetComponent 'vcf01-sddcmgr01.example.com' ` -Detail '3 stale lock rows older than 24h found in platform.lock' ` -ValidationCriteria 'No lock rows with acquired_at older than 24 hours' ` -Remediation 'Contact Broadcom support before proceeding; do not manually delete lock rows.' ` -StartedAt $started -CompletedAt (Get-Date) #> [CmdletBinding()] [OutputType([PSCustomObject])] Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$CheckId, [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$Area = '', [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$DisplayName = '', [Parameter(Mandatory = $true)] [ValidateSet('Pass', 'Warning', 'Fail', 'Error', 'Skipped')] [String]$Status, [Parameter(Mandatory = $false)] [Switch]$Blocking, [Parameter(Mandatory = $false)] [Switch]$Informational, [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$TargetComponent = '', [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$Destination = '', [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$Detail = '', [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$SkipReasonTag = '', [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$Information = '', [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$ValidationCriteria = '', [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$Remediation = '', [Parameter(Mandatory = $false)] [Nullable[DateTime]]$StartedAt = $null, [Parameter(Mandatory = $false)] [Nullable[DateTime]]$CompletedAt = $null, [Parameter(Mandatory = $false)] [AllowNull()] [String]$Exception = $null, [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$Domain = '', [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$DomainType = '', [Parameter(Mandatory = $false)] [AllowEmptyString()] [String]$Component = '', [Parameter(Mandatory = $false)] [AllowNull()] [Object[]]$Rows = @(), [Parameter(Mandatory = $false)] [AllowNull()] [Object[]]$HostDetails = @(), [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$HostDetailsLabel = 'Hosts' ) $validAreas = @('Aria Suite', 'ESX', 'NSX', 'SDDC Manager', 'vCenter', 'vSAN', 'Tanzu', 'Sample') $catalogEntry = (Get-VcfCheckCatalog)[$CheckId] $resolvedArea = if ($PSBoundParameters.ContainsKey('Area') -and -not [String]::IsNullOrWhiteSpace($Area)) { $Area } elseif ($catalogEntry) { [String]$catalogEntry.area } else { '' } $resolvedDisplayName = if ($PSBoundParameters.ContainsKey('DisplayName') -and -not [String]::IsNullOrWhiteSpace($DisplayName)) { $DisplayName } elseif ($catalogEntry) { [String]$catalogEntry.displayName } else { '' } $resolvedBlocking = if ($PSBoundParameters.ContainsKey('Blocking')) { [bool]$Blocking } elseif ($catalogEntry) { [bool]$catalogEntry.blocking } else { $false } $resolvedInformational = if ($PSBoundParameters.ContainsKey('Informational')) { [bool]$Informational } elseif ($catalogEntry) { [bool]$catalogEntry.informational } else { $false } $resolvedValidationCriteria = if ($PSBoundParameters.ContainsKey('ValidationCriteria')) { $ValidationCriteria } elseif ($catalogEntry -and -not [String]::IsNullOrWhiteSpace($catalogEntry.validationCriteria)) { [String]$catalogEntry.validationCriteria } else { $null } $resolvedRemediation = if ($PSBoundParameters.ContainsKey('Remediation')) { $Remediation } elseif ($catalogEntry -and -not [String]::IsNullOrWhiteSpace($catalogEntry.remediation)) { [String]$catalogEntry.remediation } else { $null } $resolvedInformation = if ($PSBoundParameters.ContainsKey('Information')) { $Information } elseif ($catalogEntry -and -not [String]::IsNullOrWhiteSpace($catalogEntry.information)) { [String]$catalogEntry.information } else { $null } if ([String]::IsNullOrWhiteSpace($resolvedArea) -or $resolvedArea -notin $validAreas) { throw [System.InvalidOperationException]::new("New-VcfCheckResult: check id `"$CheckId`" has no catalog entry to resolve Area from, and no explicit -Area was supplied. Pass -Area explicitly for a check id that is not in Data/CheckCatalog.json.") } if ([String]::IsNullOrWhiteSpace($resolvedDisplayName)) { throw [System.InvalidOperationException]::new("New-VcfCheckResult: check id `"$CheckId`" has no catalog entry to resolve DisplayName from, and no explicit -DisplayName was supplied. Pass -DisplayName explicitly for a check id that is not in Data/CheckCatalog.json.") } $durationMs = $null if ($null -ne $StartedAt -and $null -ne $CompletedAt) { $durationMs = ($CompletedAt - $StartedAt).TotalMilliseconds } $resolvedRows = @() if ($null -ne $Rows) { $resolvedRows = $Rows } $resolvedHostDetails = @() if ($null -ne $HostDetails) { $resolvedHostDetails = $HostDetails } return [PSCustomObject]@{ PSTypeName = 'VcfCheck.Result' CheckId = $CheckId Area = $resolvedArea DisplayName = $resolvedDisplayName Status = $Status Blocking = $resolvedBlocking Informational = $resolvedInformational TargetComponent = $TargetComponent Destination = $Destination Domain = $Domain DomainType = $DomainType Component = $Component Detail = Protect-VcfCheckLogMessage -Message $Detail SkipReasonTag = if ($Status -eq 'Skipped') { Protect-VcfCheckLogMessage -Message $SkipReasonTag } else { $null } Information = $resolvedInformation ValidationCriteria = $resolvedValidationCriteria Remediation = $resolvedRemediation StartedAt = $StartedAt CompletedAt = $CompletedAt DurationMs = $durationMs Exception = if ($null -ne $Exception) { Protect-VcfCheckLogMessage -Message $Exception } else { $null } Rows = $resolvedRows HostDetails = $resolvedHostDetails HostDetailsLabel = $HostDetailsLabel } } function Get-VcfCheckBlockingStatusFromCatalog { <# .SYNOPSIS Retrieves the blocking status for a check from the catalog. .DESCRIPTION Looks up a check ID in CheckCatalog.json and returns its blocking flag. This is a convenience wrapper to avoid duplicating the catalog lookup logic across check functions. .PARAMETER CheckId The check identifier to look up in the catalog. .OUTPUTS [Bool] True if the check is catalog-marked as blocking, false otherwise. #> [CmdletBinding()] [OutputType([Bool])] Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$CheckId ) $catalog = Get-VcfCheckCatalog if ($catalog -and $catalog[$CheckId]) { return [bool]$catalog[$CheckId].blocking } return $false } function New-VcfCheckContext { <# .SYNOPSIS Builds the explicit connection/credential context object passed to every check. .DESCRIPTION Creates an explicit context object that holds all shared state (connections, credentials, resolved FQDNs, caches) used by check functions. Every Test-Vcf<CheckId> function receives this context and reads/writes through it, making checks testable in isolation and centralizing the connect-once-reuse-everywhere behavior in one place. .PARAMETER Settings The settings object returned by Get-VcfCheckSettings. .OUTPUTS [PSCustomObject] with PSTypeName 'VcfCheck.Context'. .NOTES ComponentCredentialCache is keyed by "<ResourceType>|<AccountType>|<Fqdn>|<Username>" (see Get-VcfCheckComponentCredential) rather than one dictionary per resource type - live testing against a real SDDC Manager showed the credentials API can return several distinct accounts (e.g. admin/audit/root) for the same resource + account type, so the cache key has to disambiguate on Username too. SddcManagerRootCredential is separate from ComponentCredentialCache: SDDC Manager's own appliance root/OS account is NOT retrievable via the VCF credentials API (confirmed against a live lab - it never appears in Invoke-VcfGetCredentials output), so it is always resolved via a dedicated interactive prompt (Get-VcfCheckSddcManagerRootCredential) and cached here instead. VrslcmConnection is separate from ComponentCredentialCache for a different reason: Aria Suite Lifecycle Manager (VRSLCM) is a fleet-wide shared service with no per-domain FQDN lookup (unlike vCenter/NSX Manager, which are resolved from a known domain first) - its FQDN can only be discovered from the credential API response itself (Invoke-VcfGetCredentials -ResourceType VRSLCM -> Elements[].Resource.Fqdn), so the Fqdn/Credential pair is resolved and cached together by Get-VcfCheckVrslcmConnection rather than looked up by a caller-supplied Fqdn. VrslcmRootCredential is cached separately from VrslcmConnection because it is a different SDDC Manager credential entry (CredentialType 'SSH', not 'API') - see Get-VcfCheckVrslcmRootCredential. AriaOpsCredential/AriaOpsConnection/UnreachableAriaOps mirror the VrslcmConnection / UnreachableVCenters caches for Aria Operations - see Get-VcfCheckAriaOpsCredential and Connect-VcfCheckAriaOps in Private/AriaOpsHelpers.ps1. Aria Operations has full SDK coverage (VMware.Sdk.Vcf.Ops), so unlike VRSLCM there is no separate REST-API-session cache - Invoke-VcfOps* cmdlets take AriaOpsConnection directly via -Server. VCenterApiSessions caches vSphere Automation API session tokens (POST /api/session) keyed by vCenter FQDN, for checks that call vCenter's own REST API directly rather than through PowerCLI (e.g. namespace-management, appliance health/proxy endpoints) - see Private/VCenterApiHelpers.ps1. AllVCenterFqdns caches every vCenter FQDN SDDC Manager knows about (management domain and every workload domain), resolved by Get-VcfCheckAllVCenterFqdns. Checks that inspect vCenter/ESXi/vSAN inventory iterate this list rather than the single ManagementVCenterFqdn, so every workload domain's clusters/hosts are covered, not just the management domain's. VCenterDomainTypesByFqdn is the DomainType sibling of VCenterDomainsByFqdn (kept as a separate cache rather than changing that dictionary's value shape, so existing consumers of the name-only cache are unaffected) - see Get-VcfCheckVCenterDomainType. DomainsByName caches the full domain object (Name, SsoName, IsManagementSsoDomain, Type, VCenters, etc.) returned by Invoke-VcfGetDomains, keyed by domain name - populated alongside VCenterDomainsByFqdn by Get-VcfCheckAllVCenterFqdns. Needed by Get-VcfCheckVCenterSsoCredential to tell an isolated-SSO workload domain (its own PSC/ SYSTEM credential) from one that joined the Management domain's shared SSO domain (must use the Management domain's own credential instead) - a vCenter's PSC/SYSTEM credential in SDDC Manager is registered against the owning SSO domain's name, not the vCenter's own FQDN, so looking it up by Fqdn silently returns no match for a workload domain vCenter. SelectedDomains carries Invoke-VcfCheck's -Domain run-scope filter (domain names, empty array = no filtering) so Get-VcfCheckAllVCenterFqdns can exclude out-of-scope domains' vCenters before any check connects to them. OutputPath is Invoke-VcfCheck's resolved findings directory (the same directory latest.json is written to), set once before the check loop starts. It lets a long-running check report sub-progress (e.g. "host 3/12") via Write-VcfCheckSubProgress without needing its own -OutputPath parameter. AllowInsecureTls is the resolved, effective decision for this run on whether to accept untrusted/self-signed TLS certificates - derived entirely from PowerCLI's own InvalidCertificateAction setting (see Invoke-VcfCheck in Orchestrator.ps1; there is no VcfCheck-specific setting). Every connector that would otherwise unconditionally bypass TLS certificate validation (Aria Automation/Aria Operations/VRSLCM/NSX Manager REST helpers) reads this instead of hard-coding the bypass, so an operator who has not opted in (via PowerCLI's own configuration) gets a clear connection failure instead of a silently accepted untrusted certificate. AriaAutomationCredential caches the resolved Fqdn/Credential/AllowInsecureTls object for Aria Automation - see Get-VcfCheckAriaAutomationCredential in Private/AriaAutomationHelpers.ps1. A single cache entry is sufficient because, unlike VRSLCM/Aria Operations, Aria Automation is looked up once per run and reused by every check that calls Invoke-VcfCheckAriaAutomationApi. UnreachableVCenters caches, per vCenter FQDN, the reason Connect-VcfCheckVCenter last failed to reach or authenticate to it. Confirmed live: when a vCenter drops mid-run, every remaining check that targets it independently re-attempts the connection and each surfaces its own raw PowerCLI exception text ("Server X is not connected" from Get-VIMachineCertificate, a different message from Get-VMHost, etc.) - a confusing, repetitive report for one real outage. Connect-VcfCheckVCenter checks this cache first and fails fast with the same message every time instead of re-running the TCP/auth attempt (and its timeout) for every check that targets the same dead vCenter. #> [CmdletBinding()] [OutputType([PSCustomObject])] Param ( [Parameter(Mandatory = $false)] [AllowNull()] [PSObject]$Settings = $null ) return [PSCustomObject]@{ PSTypeName = 'VcfCheck.Context' Settings = $Settings SddcManagerFqdn = $null SddcManagerConnection = $null SddcManagerRootCredential = $null ManagementVCenterFqdn = $null ManagementNsxManagerFqdn = $null ManagementDomainId = $null ManagementDomainObject = $null AllVCenterFqdns = $null VcfVersion = $null VCenterDomainsByFqdn = @{} VCenterDomainTypesByFqdn = @{} DomainsByName = @{} SelectedDomains = @() AllowInsecureTls = $false VrslcmConnection = $null VrslcmRootCredential = $null AriaOpsCredential = $null AriaOpsConnection = $null UnreachableAriaOps = $null AriaOpsEndpointConnections = @{} UnreachableAriaOpsEndpoints = @{} AriaOpsEndpoints = @() AriaOpsEndpointCredentials = @{} AriaAutomationCredential = $null VCenterApiSessions = @{} ComponentCredentialCache = @{} ConnectedVCenters = [System.Collections.Generic.List[String]]::new() ConnectedNsxManagers = [System.Collections.Generic.List[String]]::new() UnreachableVCenters = @{} LogPath = $null OutputPath = $null } } #endregion Models |