Private/Configuration.ps1
|
<# This is the first function called in the connector workflow. At this stage, the logger is not initialized yet, so this function must not rely on `Write-CustomLog` (or any logging target setup). It should fail fast with clear exceptions when configuration is missing or invalid. #> function Get-Configuration { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$ScriptRootPath, [Parameter(Mandatory = $true)] [string]$NutanixEnvironment ) $configPath = Join-Path -Path $ScriptRootPath -ChildPath (Join-Path -Path "Config" -ChildPath "config.json") if (-not (Test-Path -Path $configPath)) { # The repository uses lowercase config/, while packaged Windows bundles use Config/. $configPath = Join-Path -Path $ScriptRootPath -ChildPath (Join-Path -Path "config" -ChildPath "config.json") } if (-not (Test-Path -Path $configPath)) { throw "Configuration file not found at: $configPath" } $raw = Get-Content -Path $configPath -Raw -ErrorAction Stop if ([string]::IsNullOrWhiteSpace($raw)) { throw "Invalid configuration: configuration file is empty. Path: $configPath" } try { $config = $raw | ConvertFrom-Json -ErrorAction Stop } catch { throw "Invalid configuration: configuration file is not valid JSON. Path: $configPath. Error: $($_.Exception.Message)" } if ($null -eq $config) { throw "Invalid configuration: config object is null. Path: $configPath" } # Fail fast on required high-level structure so later environment selection doesn't produce misleading errors. if ($null -eq $config.nutanixEnvironments -or -not ($config.nutanixEnvironments -is [System.Array])) { throw "Invalid configuration: 'nutanixEnvironments' is required and must be an array." } if ($config.nutanixEnvironments.Count -eq 0) { throw "Invalid configuration: 'nutanixEnvironments' must not be empty." } $envConfig = $config.nutanixEnvironments | Where-Object { $_.Name -eq $NutanixEnvironment } if ($null -eq $envConfig) { throw "Environment '$NutanixEnvironment' not found in configuration" } Test-ConnectorConfiguration -Config $config -EnvironmentConfig $envConfig $typedEnvConfig = [NutanixEnvironmentConfiguration]::new() $typedEnvConfig.Name = $envConfig.Name $typedEnvConfig.PrismCentralFQDN = $envConfig.PrismCentralFQDN if ($null -ne $envConfig.PSObject.Properties['PrismCentralPort']) { $typedEnvConfig.PrismCentralPort = [int]$envConfig.PrismCentralPort } $typedEnvConfig.WindowsCredentialEntry = $envConfig.WindowsCredentialEntry $typedEnvConfig.SkipCertificateCheck = $false if ($null -ne $envConfig.PSObject.Properties['SkipCertificateCheck']) { $typedEnvConfig.SkipCertificateCheck = [bool]$envConfig.SkipCertificateCheck } $typedNexthinkApi = [NutanixNexthinkApiConfiguration]::new() $typedNexthinkApi.HostFQDN = $config.NexthinkAPI.HostFQDN $typedNexthinkApi.LoginFQDN = $config.NexthinkAPI.LoginFQDN $typedNexthinkApi.WindowsCredentialEntry = $config.NexthinkAPI.WindowsCredentialEntry $typedNexthinkApi.RequestBatchSize = [int]$config.NexthinkAPI.RequestBatchSize $typedNexthinkApi.VmCacheExpiration = $script:VM_CACHE_EXPIRATION if (Test-ConfigurationCacheExpiration -Value $config.NexthinkAPI.VmCacheExpiration) { $typedNexthinkApi.VmCacheExpiration = $config.NexthinkAPI.VmCacheExpiration } $typedLogging = $null if ($null -ne $config.Logging) { $typedLogging = [NutanixLoggingConfiguration]::new() $typedLogging.LogLevel = $config.Logging.LogLevel $typedLogging.LogRetentionDays = $config.Logging.LogRetentionDays } $cfg = [NutanixConnectorConfiguration]::new() $cfg.ScriptRootPath = $ScriptRootPath $cfg.EnvironmentConfig = $typedEnvConfig $cfg.NexthinkAPI = $typedNexthinkApi $cfg.Logging = $typedLogging return $cfg } function Stop-ConfigError { param([string]$Message) Write-Error -Message $Message throw $Message } function Assert-ConfigurationNonEmptyString { param( [string]$Value, [string]$FieldName ) if ([string]::IsNullOrWhiteSpace($Value)) { Stop-ConfigError "Invalid configuration: '$FieldName' is required and must be a non-empty string." } } function Test-ConfigurationPort { param([object]$EnvironmentConfig) if ($null -eq $EnvironmentConfig.PSObject.Properties['PrismCentralPort']) { return } $port = 0 if (-not [int]::TryParse([string]$EnvironmentConfig.PrismCentralPort, [ref]$port) -or $port -lt 1 -or $port -gt 65535) { Stop-ConfigError "Invalid configuration: 'nutanixEnvironments[].PrismCentralPort' must be between 1 and 65535." } } function Test-ConfigurationSkipCertificateCheck { param([object]$EnvironmentConfig) if ($null -ne $EnvironmentConfig.PSObject.Properties['SkipCertificateCheck'] -and $EnvironmentConfig.SkipCertificateCheck -isnot [bool]) { Stop-ConfigError "Invalid configuration: 'nutanixEnvironments[].SkipCertificateCheck' must be a JSON boolean." } } function Test-ConfigurationLogging { param([object]$Logging) if ($null -eq $Logging) { return } if ($null -ne $Logging.LogLevel -and -not [string]::IsNullOrWhiteSpace([string]$Logging.LogLevel)) { $allowed = @('DEBUG', 'INFO', 'WARNING', 'ERROR') if ($allowed -notcontains ([string]$Logging.LogLevel).ToUpperInvariant()) { Stop-ConfigError "Invalid configuration: 'Logging.LogLevel' must be one of: $($allowed -join ', '). Value: '$($Logging.LogLevel)'" } } if ($null -ne $Logging.LogRetentionDays -and -not [string]::IsNullOrWhiteSpace([string]$Logging.LogRetentionDays)) { $retention = 0 if (-not [int]::TryParse([string]$Logging.LogRetentionDays, [ref]$retention) -or $retention -le 0) { Stop-ConfigError "Invalid configuration: 'Logging.LogRetentionDays' must be a positive integer. Value: '$($Logging.LogRetentionDays)'" } $Logging.LogRetentionDays = $retention } } function Test-ConfigurationNexthinkApi { param([object]$NexthinkApi) if ($null -eq $NexthinkApi) { Stop-ConfigError "Invalid configuration: 'NexthinkAPI' section is required." } Assert-ConfigurationNonEmptyString -Value $NexthinkApi.HostFQDN -FieldName 'NexthinkAPI.HostFQDN' Assert-ConfigurationNonEmptyString -Value $NexthinkApi.LoginFQDN -FieldName 'NexthinkAPI.LoginFQDN' Assert-ConfigurationNonEmptyString -Value $NexthinkApi.WindowsCredentialEntry -FieldName 'NexthinkAPI.WindowsCredentialEntry' $batchSize = 0 if ($null -eq $NexthinkApi.RequestBatchSize -or -not [int]::TryParse([string]$NexthinkApi.RequestBatchSize, [ref]$batchSize) -or $batchSize -le 0) { Stop-ConfigError "Invalid configuration: 'NexthinkAPI.RequestBatchSize' must be a positive integer." } $NexthinkApi.RequestBatchSize = $batchSize $null = Test-ConfigurationCacheExpiration -Value $NexthinkApi.VmCacheExpiration } function Test-ConnectorConfiguration { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [AllowNull()] [object]$Config, [Parameter(Mandatory = $true)] [object]$EnvironmentConfig ) Write-CustomLog -Message "Validating configuration..." -Severity 'DEBUG' if ($null -eq $Config) { Stop-ConfigError "Invalid configuration: config object is null." } if ($null -eq $Config.nutanixEnvironments -or -not ($Config.nutanixEnvironments -is [System.Array])) { Stop-ConfigError "Invalid configuration: 'nutanixEnvironments' is required and must be an array." } if ($Config.nutanixEnvironments.Count -eq 0) { Stop-ConfigError "Invalid configuration: 'nutanixEnvironments' must not be empty." } # Validate selected environment config Assert-ConfigurationNonEmptyString -Value $EnvironmentConfig.Name -FieldName 'nutanixEnvironments[].Name' Assert-ConfigurationNonEmptyString -Value $EnvironmentConfig.PrismCentralFQDN -FieldName 'nutanixEnvironments[].PrismCentralFQDN' Assert-ConfigurationNonEmptyString -Value $EnvironmentConfig.WindowsCredentialEntry -FieldName 'nutanixEnvironments[].WindowsCredentialEntry' Test-ConfigurationPort -EnvironmentConfig $EnvironmentConfig Test-ConfigurationSkipCertificateCheck -EnvironmentConfig $EnvironmentConfig Test-ConfigurationNexthinkApi -NexthinkApi $Config.NexthinkAPI # Optional logging settings validation Test-ConfigurationLogging -Logging $Config.Logging Write-CustomLog -Message "Configuration validated successfully." -Severity 'DEBUG' } function Test-ConfigurationCacheExpiration { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [AllowNull()] $Value ) if ($null -eq $Value -or [string]::IsNullOrWhiteSpace([string]$Value)) { return $false } $valueStr = [string]$Value try { $null = [System.Xml.XmlConvert]::ToTimeSpan($valueStr) return $true } catch { throw "Invalid configuration: 'VmCacheExpiration' must be an ISO 8601 duration (e.g. 'P1D', 'PT1H', 'PT30M'). Value: '$valueStr'" } } |