Private/Project/Test-TcProjectDefinition.ps1
|
function Test-TcProjectDefinition { <# .SYNOPSIS Validates a project and returns one record per problem. .DESCRIPTION This is the only place that decides whether a project is valid. It returns records with Code, Path and Message, and returns nothing when the project is valid. It tolerates any shape of input because project files can be edited by hand. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)][AllowNull()]$Project ) function Add-Problem { param([string]$Code, [string]$Path, [string]$Message) [pscustomobject]@{ Code = $Code; Path = $Path; Message = $Message } } function Test-Whole { param($Value) ($Value -is [int] -or $Value -is [long]) -and $Value -ge 1 } function Test-Tenant { param($Tenant, [string]$Path) if (-not (Test-TcDisplayName -Value (Get-TcProperty $Tenant 'displayName'))) { Add-Problem 'TC1010' "$Path.displayName" 'A display name is required, up to 64 characters, with no control characters.' } $domain = ConvertTo-TcDomain -Value (Get-TcProperty $Tenant 'primaryDomain') if (-not (Test-TcDomainName -Value $domain)) { Add-Problem 'TC1004' "$Path.primaryDomain" "'$domain' is not a valid domain name." } $tenantId = Get-TcProperty $Tenant 'tenantId' $parsed = [guid]::Empty if ($null -ne $tenantId -and -not [guid]::TryParse([string]$tenantId, [ref]$parsed)) { Add-Problem 'TC1005' "$Path.tenantId" "'$tenantId' is not a valid tenant ID." } } $version = Get-TcProperty $Project 'schemaVersion' if ($version -ne $script:TcSchemaVersion -or -not (Test-Whole $version)) { $shown = if ($null -eq $version) { 'none' } else { $version } Add-Problem 'TC1008' 'schemaVersion' "This project uses schema version $shown. This module reads version $script:TcSchemaVersion." } $target = Get-TcProperty $Project 'target' if ($null -eq $target) { Add-Problem 'TC1003' 'target' 'A project needs exactly 1 target tenant.' } else { Test-Tenant $target 'target' if ((Get-TcProperty $target 'state') -cnotin 'Production', 'New') { Add-Problem 'TC1013' 'target.state' 'Target state must be Production or New.' } } # Get-TcProperty unrolls a list of one, so the shape is read from the property itself. $holder = if ($Project -is [System.Collections.IDictionary]) { [pscustomobject]$Project } else { $Project } $listed = if ($null -ne $holder) { $holder.PSObject.Properties['sources'] } if ($listed -and $null -ne $listed.Value -and $listed.Value -isnot [System.Collections.IList]) { Add-Problem 'TC1016' 'sources' 'Sources must be a list, in [ ], even for one source.' } $sources = @(Get-TcProperty $Project 'sources' | Where-Object { $null -ne $_ }) if ($sources.Count -eq 0) { Add-Problem 'TC1001' 'sources' 'A project needs at least 1 source tenant.' } elseif ($sources.Count -gt $script:TcMaxSources) { Add-Problem 'TC1002' 'sources' "TenantConverge supports at most $script:TcMaxSources source tenants into 1 target. This project has $($sources.Count)." } else { for ($i = 0; $i -lt $sources.Count; $i++) { if ((Get-TcProperty $sources[$i] 'role') -cne "S$($i + 1)") { Add-Problem 'TC1009' 'sources' 'Source roles must be S1, S2, S3, in order.' break } } } for ($i = 0; $i -lt $sources.Count; $i++) { Test-Tenant $sources[$i] "sources[$i]" } $seen = @{} foreach ($tenant in @($target) + $sources) { $keys = @(ConvertTo-TcDomain -Value (Get-TcProperty $tenant 'primaryDomain')) $tenantId = Get-TcProperty $tenant 'tenantId' if ($null -ne $tenantId) { $keys += ([string]$tenantId).ToLowerInvariant() } foreach ($key in $keys) { if (-not $key) { continue } if ($seen.ContainsKey($key)) { Add-Problem 'TC1006' 'sources' "'$key' appears more than once. Each tenant can only appear once in a project." } $seen[$key] = $true } } if ($sources.Count -gt 0) { $waves = @($sources | ForEach-Object { Get-TcProperty $_ 'wave' }) $whole = ($waves.Count -eq $sources.Count) -and (@($waves | Where-Object { -not (Test-Whole $_) }).Count -eq 0) if (-not $whole) { Add-Problem 'TC1007' 'sources' "Each source needs a wave, a whole number from 1. Found: $($waves -join ', ')." } else { $distinct = @($waves | Sort-Object -Unique) if (($distinct -join ',') -ne ((1..$distinct.Count) -join ',')) { Add-Problem 'TC1007' 'sources' "Waves must start at 1 and have no gaps. Found: $($waves -join ', ')." } } } if ((Get-TcProperty (Get-TcProperty $Project 'access') 'mode') -cnotin 'Browser', 'Application') { Add-Problem 'TC1011' 'access.mode' 'Access mode must be Browser or Application.' } if ([string]::IsNullOrWhiteSpace([string](Get-TcProperty (Get-TcProperty $Project 'output') 'path'))) { Add-Problem 'TC1012' 'output.path' 'An output folder is required.' } } |