PSSwaggerMetadata.psm1
######################################################################################### # # Copyright (c) Microsoft Corporation. All rights reserved. # # Licensed under the MIT license. # # PSSwagger Module # ######################################################################################### Microsoft.PowerShell.Core\Set-StrictMode -Version Latest $SubScripts = @( 'PSSwagger.Constants.ps1' ) $SubScripts | ForEach-Object {. (Join-Path -Path $PSScriptRoot -ChildPath $_) -Force} $SubModules = @( 'SwaggerUtils.psm1', 'Utilities.psm1', 'Paths.psm1', 'Definitions.psm1' ) $SubModules | ForEach-Object {Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath $_) -Force -Scope Local -DisableNameChecking} Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PSSwagger.Resources.psd1 <# .SYNOPSIS Creates PowerShell Metadata json file with PowerShell Extensions for the specified Swagger document. .DESCRIPTION Creates PowerShell Metadata json file with PowerShell Extensions for the specified Swagger document. This file can be used to customize the PowerShell specific metadata like cmdlet name, parameter name, output format views, code generation settings, PowerShell Module metadata and other related metadata. PowerShell Metadata file name for <SwaggerSpecFileName>.json is <SwaggerSpecFileName>.psmeta.json. This <SwaggerSpecFileName>.psmeta.json file gets created under the same location as the specified swagger document path. .EXAMPLE PS> New-PSSwaggerMetadataFile -SpecificationPath 'C:\SwaggerSpecs\BatchManagement.json' Generates 'C:\SwaggerSpecs\BatchManagement.psmeta.json' file with PowerShell extensions for customizing the PowerShell related metadata. .EXAMPLE PS> New-PSSwaggerMetadataFile -SpecificationPath 'C:\SwaggerSpecs\BatchManagement.json' -Force Regenerates 'C:\SwaggerSpecs\BatchManagement.psmeta.json' file with PowerShell extensions for customizing the PowerShell related metadata. .PARAMETER SpecificationPath Full Path to a Swagger based JSON spec. .PARAMETER Force To replace the existing PowerShell Metadata file. .INPUTS .OUTPUTS .NOTES .LINK #> function New-PSSwaggerMetadataFile { [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter( Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)] [string] $SpecificationPath, [Parameter(Mandatory = $false)] [switch] $Force ) # Validate swagger path if (-not (Test-path -Path $SpecificationPath -PathType Leaf)) { throw $LocalizedData.SwaggerSpecPathNotExist -f ($SpecificationPath) return } $PSMetaFilePath = [regex]::replace($SpecificationPath, ".json$", ".psmeta.json") if ((-not $Force) -and (Test-Path -Path $PSMetaFilePath -PathType Leaf)) { Throw $LocalizedData.PSMetaFileExists -f ($PSMetaFilePath, $SpecificationPath) } $SwaggerSpecFilePaths = @() $AutoRestModeler = 'Swagger' $jsonObject = ConvertFrom-Json -InputObject ((Get-Content -Path $SpecificationPath) -join [Environment]::NewLine) -ErrorAction Stop $SwaggerBaseDir = Split-Path -Path $SpecificationPath -Parent if ((Get-Member -InputObject $jsonObject -Name 'Documents') -and ($jsonObject.Documents.Count)) { $AutoRestModeler = 'CompositeSwagger' foreach ($document in $jsonObject.Documents) { if (Test-Path -Path $document -PathType Leaf) { $SwaggerSpecFilePaths += $document } elseif (Test-Path -Path (Join-Path -Path $SwaggerBaseDir -ChildPath $document) -PathType Leaf) { $SwaggerSpecFilePaths += Join-Path -Path $SwaggerBaseDir -ChildPath $document } else { throw $LocalizedData.PathNotFound -f ($document) return } } } else { $SwaggerSpecFilePaths += $SpecificationPath } $DefinitionFunctionsDetails = @{} $ParameterGroupCache = @{} $PathFunctionDetails = @{} $x_ms_path_FunctionDetails = @{} # Parse the JSON and populate the dictionary $ConvertToSwaggerDictionary_params = @{ SwaggerSpecPath = $SpecificationPath SwaggerSpecFilePaths = $SwaggerSpecFilePaths DefinitionFunctionsDetails = $DefinitionFunctionsDetails } $swaggerDict = ConvertTo-SwaggerDictionary @ConvertToSwaggerDictionary_params $nameSpace = $swaggerDict['info'].NameSpace $models = $swaggerDict['info'].Models $swaggerMetaDict = @{ SwaggerSpecPath = $SpecificationPath SwaggerSpecFilePaths = $SwaggerSpecFilePaths AutoRestModeler = $AutoRestModeler } foreach ($FilePath in $SwaggerSpecFilePaths) { $jsonObject = ConvertFrom-Json -InputObject ((Get-Content -Path $FilePath) -join [Environment]::NewLine) -ErrorAction Stop if (Get-Member -InputObject $jsonObject -Name 'Definitions') { # Handle the Definitions $jsonObject.Definitions.PSObject.Properties | ForEach-Object { $GetSwaggerSpecDefinitionInfo_params = @{ JsonDefinitionItemObject = $_ Namespace = $Namespace DefinitionFunctionsDetails = $DefinitionFunctionsDetails Models = $models } Get-SwaggerSpecDefinitionInfo @GetSwaggerSpecDefinitionInfo_params } } if (Get-Member -InputObject $jsonObject -Name 'Paths') { # Handle the Paths $jsonObject.Paths.PSObject.Properties | ForEach-Object { $GetSwaggerSpecPathInfo_params = @{ JsonPathItemObject = $_ PathFunctionDetails = $PathFunctionDetails SwaggerDict = $swaggerDict SwaggerMetaDict = $swaggerMetaDict DefinitionFunctionsDetails = $DefinitionFunctionsDetails ParameterGroupCache = $ParameterGroupCache } Get-SwaggerSpecPathInfo @GetSwaggerSpecPathInfo_params } } if (Get-Member -InputObject $jsonObject -Name 'x-ms-paths') { # Handle extended paths $jsonObject.'x-ms-paths'.PSObject.Properties | ForEach-Object { $GetSwaggerSpecPathInfo_params = @{ JsonPathItemObject = $_ PathFunctionDetails = $x_ms_path_FunctionDetails SwaggerDict = $swaggerDict SwaggerMetaDict = $swaggerMetaDict DefinitionFunctionsDetails = $DefinitionFunctionsDetails ParameterGroupCache = $ParameterGroupCache } Get-SwaggerSpecPathInfo @GetSwaggerSpecPathInfo_params } } } $infoMetadata = Get-InfoPSMetadata -SwaggerDict $swaggerDict $definitionsMetadata = Get-DefinitionsPSMetadata -DefinitionFunctionsDetails $DefinitionFunctionsDetails -SwaggerDict $swaggerDict $pathsMetadata = Get-PathsPSMetadata -PathFunctionDetails $PathFunctionDetails $globalParametersMetadata = Get-GlobalParametersPSMetadata -SwaggerDict $swaggerDict $psMetadata = [ordered]@{ info = $infoMetadata paths = $pathsMetadata } # Add x-ms-paths key if there are any swagger operations are specified under x-ms-paths. if (Get-HashtableKeyCount -Hashtable $x_ms_path_FunctionDetails) { $x_ms_pathsMetadata = Get-PathsPSMetadata -PathFunctionDetails $x_ms_path_FunctionDetails $psMetadata['x-ms-paths'] = $x_ms_pathsMetadata } $psMetadata['definitions'] = $definitionsMetadata $psMetadata['parameters'] = $globalParametersMetadata $psmetaJson = ConvertTo-Json -InputObject $psMetadata -Depth 100 | Format-JsonUtility if ($psmetaJson -and ($Force -or $pscmdlet.ShouldProcess($PSMetaFilePath, $LocalizedData.NewPSSwaggerMetadataFileOperationMessage))) { $OutFile_Params = @{ InputObject = $psmetaJson FilePath = $PSMetaFilePath Encoding = 'ascii' Force = $true Confirm = $false WhatIf = $false } Out-File @OutFile_Params Write-Verbose -Message ($LocalizedData.SuccessfullyGeneratedMetadataFile -f $PSMetaFilePath, $SpecificationPath) } } #region PSSwaggerMetadata Utilities <# Helper function for getting the code generation settings and module info metadata. #> function Get-InfoPSMetadata { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [PSCustomObject] $SwaggerDict ) $PSCodeGenerationSettings = [ordered]@{ codeGenerator = 'CSharp' nameSpacePrefix = 'Microsoft.PowerShell.' noAssembly = $false powerShellCorePath = '' includeCoreFxAssembly = $false testBuild = $false confirmBootstrap = $false path = '.' symbolPath = '.' serviceType = '' customAuthCommand = '' hostOverrideCommand = '' noAuthChallenge = '' } $PSModuleInfo = [ordered]@{ name = $swaggerDict['Info'].ModuleName moduleVersion = $swaggerDict['Info'].Version.ToString() guid = [guid]::NewGuid() description = $swaggerDict['Info'].Description author = $swaggerDict['Info'].ContactEmail companyName = '' CopyRight = $swaggerDict['Info'].LicenseName licenseUri = $swaggerDict['Info'].LicenseUri projectUri = $swaggerDict['Info'].ProjectUri helpInfoUri = '' iconUri = '' releaseNotes = '' defaultCommandPrefix = '' tags = @() } return [ordered]@{ 'x-ps-code-generation-settings' = $PSCodeGenerationSettings 'x-ps-module-info' = $PSModuleInfo } } <# Helper function for getting the definitions metadata with cmdlet infos, parameter info and output format info. #> function Get-DefinitionsPSMetadata { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [PSCustomObject] $DefinitionFunctionsDetails, [Parameter(Mandatory = $true)] [PSCustomObject] $SwaggerDict ) $definitionsMetadata = [ordered]@{} $DefinitionFunctionsDetails.GetEnumerator() | Foreach-Object { $definitionName = $_.Name $FunctionDetails = $_.Value $generateCommand = $false $generateOutputFormat = $false if ($FunctionDetails.IsModel) { $generateCommand = $true $generateOutputFormat = $true } $x_ps_cmdlet_info = [ordered]@{ name = "New-$($definitionName)Object" description = $FunctionDetails.Description defaultParameterSet = $definitionName generateCommand = $generateCommand generateOutputFormat = $generateOutputFormat } $propertiesPSMetadata = [ordered]@{} $defaultFormatViewWidth = 10 $TableColumnItemCount = 0 $ParametersCount = Get-HashtableKeyCount -Hashtable $FunctionDetails.ParametersTable $SkipParameterList = @('id', 'tags') $Namespace = $SwaggerDict['info'].NameSpace $FunctionDetails.ParametersTable.GetEnumerator() | Foreach-Object { $parameterName = $_.Name $parameterDetails = $_.Value $x_ps_parameter_info = [ordered]@{ name = $parameterName description = $parameterDetails.Description } # Enable output format view for all properties when definition has 4 or less properties. # Otherwise add the first 4 properties with basic types by skipping the complex types, id and tags. if ($FunctionDetails.IsModel -and (($ParametersCount -le 4) -or (($TableColumnItemCount -le 4) -and ($SkipParameterList -notcontains $parameterDetails.Name) -and (-not $ParameterDetails.Type.StartsWith($Namespace, [System.StringComparison]::OrdinalIgnoreCase))))) { $includeInOutputFormat = $true $formatViewPosition = $TableColumnItemCount $TableColumnItemCount = $TableColumnItemCount + 1 } else { $includeInOutputFormat = $false # Position is specified as -1 so that module owner can edit this value with proper position number # if he/she decides to enable the output format for the specific property. $formatViewPosition = -1 } $x_ps_output_format_info = [ordered]@{ include = $includeInOutputFormat position = $formatViewPosition width = $defaultFormatViewWidth } $propertiesPSMetadata[$parameterDetails.OriginalParameterName] = [ordered]@{ 'x-ps-parameter-info' = $x_ps_parameter_info 'x-ps-output-format-info' = $x_ps_output_format_info } } $definitionsMetadata[$definitionName] = [ordered]@{ 'x-ps-cmdlet-infos' = @($x_ps_cmdlet_info) properties = $propertiesPSMetadata } } return $definitionsMetadata } <# Helper function for getting the paths metadata with cmdlet infos and parameter info. #> function Get-PathsPSMetadata { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [PSCustomObject] $PathFunctionDetails ) $pathsMetadata = [ordered]@{} $PathFunctionDetails.GetEnumerator() | Foreach-Object { $CommandName = $_.Name $FunctionDetails = $_.Value $defaultParameterSetName = '' $FunctionDetails.ParameterSetDetails | Foreach-Object { $ParameterSetDetail = $_ # When multiple operations are combined into single cmdlet, # adding first parameterset as the default parameterset name. if (-not $defaultParameterSetName) { $defaultParameterSetName = $ParameterSetDetail.OperationId } $EndpointRelativePath = $ParameterSetDetail.EndpointRelativePath if ($pathsMetadata.Contains($EndpointRelativePath)) { $operationsPSMetadata = $pathsMetadata[$EndpointRelativePath] } else { $operationsPSMetadata = [ordered]@{} } $operationType = $ParameterSetDetail.OperationType if ($operationsPSMetadata.Contains($operationType)) { $opPSMetadata = $operationsPSMetadata[$operationType] } else { $opPSMetadata = [ordered]@{} } if ($opPSMetadata.Contains('x-ps-cmdlet-infos')) { $x_ps_cmdlet_infos = $opPSMetadata['x-ps-cmdlet-infos'] } else { $x_ps_cmdlet_infos = @() } $x_ps_cmdlet_infos += [ordered]@{ name = $CommandName description = $ParameterSetDetail.Description defaultParameterSet = $defaultParameterSetName generateCommand = $true } $opPSMetadata['x-ps-cmdlet-infos'] = $x_ps_cmdlet_infos # For multiple cmdlet scenario like CreateAndUpdate, # if parameters are already populated for one cmdlet, # it is not required to process the parameters for other cmdlet for same operationId. if (-not $opPSMetadata.Contains('parameters')) { $parametersPSMetadata = [ordered]@{} $ParameterSetDetail.ParameterDetails.GetEnumerator() | Foreach-Object { $paramDetails = $_.Value $parameterName = $paramDetails.Name if ($paramDetails.ContainsKey('OriginalParameterName') -and $paramDetails.OriginalParameterName) { $x_ps_parameter_info = [ordered]@{ name = $parameterName description = $paramDetails.Description flatten = $false } $parametersPSMetadata[$paramDetails.OriginalParameterName] = [ordered]@{ 'x-ps-parameter-info' = $x_ps_parameter_info } } } $opPSMetadata['parameters'] = $parametersPSMetadata } # Handle path level common parameters, if any $PathCommonParameters = $ParameterSetDetail.PathCommonParameters if (Get-HashtableKeyCount -Hashtable $PathCommonParameters) { $pathItemFieldName = 'parameters' if (-not $operationsPSMetadata.Contains($pathItemFieldName)) { $pathItemFieldPSMetadata = [ordered]@{} $PathCommonParameters.GetEnumerator() | Foreach-Object { $paramDetails = $_.Value $parameterName = $paramDetails.Name if ($paramDetails.ContainsKey('OriginalParameterName') -and $paramDetails.OriginalParameterName) { $x_ps_parameter_info = [ordered]@{ name = $parameterName description = $paramDetails.Description flatten = $false } $pathItemFieldPSMetadata[$paramDetails.OriginalParameterName] = [ordered]@{ 'x-ps-parameter-info' = $x_ps_parameter_info } } } $operationsPSMetadata[$pathItemFieldName] = $pathItemFieldPSMetadata } } $operationsPSMetadata[$operationType] = $opPSMetadata $pathsMetadata[$EndpointRelativePath] = $operationsPSMetadata } } return $pathsMetadata } <# Helper function for getting the parameter infos for the global parameters. #> function Get-GlobalParametersPSMetadata { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [PSCustomObject] $SwaggerDict ) $globalParametersMetadata = [ordered]@{} $SwaggerDict.Parameters.GetEnumerator() | Foreach-Object { $commonParameterKeyName = $_.Name $parameterDetails = $_.Value $x_ps_parameter_info = [ordered]@{ name = $parameterDetails.Name description = $parameterDetails.Description flatten = $false } $parameterPSMetadata = [ordered]@{ 'x-ps-parameter-info' = $x_ps_parameter_info } $globalParametersMetadata[$commonParameterKeyName] = $parameterPSMetadata } return $globalParametersMetadata } <# .DESCRIPTION Utility for formatting the json content. ConvertTo-Json cmdlet doesn't format the json content properly on Windows PowerShell. .PARAMETER Json Json string value. #> function Format-JsonUtility { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Json ) $indent = 0; ($json -Split '\n' | ForEach-Object { if (($_ -match ' [\}\]]') -or ($_ -match '[\}\]]$')) { # This line contains ] or }, decrement the indentation level if ($indent -gt 0) { $indent-- } } $line = (' ' * $indent * 2) + $_.TrimStart().Replace(': ', ': ') if (($_ -match ' [\{\[]') -or ($_ -match '^[\{\[]')) { # This line contains [ or {, increment the indentation level $indent++ } $line }) -Join "`n" } #endregion PSSwaggerMetadata Utilities Export-ModuleMember -Function New-PSSwaggerMetadataFile # SIG # Begin signature block # MIIarQYJKoZIhvcNAQcCoIIanjCCGpoCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUFj8tO9pLWD8hrqDIRSO5r+WJ # kdGgghWAMIIEwjCCA6qgAwIBAgITMwAAALm8D05X42ZlOAAAAAAAuTANBgkqhkiG # 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw # HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTYwOTA3MTc1ODQ2 # WhcNMTgwOTA3MTc1ODQ2WjCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046 # NkJGNi0yRDUyLTkyQzExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl # cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdXDM6Nw8Ck6Kk # 8k7KXa6ef90VvfETAqgHmtlBZbMr2580HCnjeUqVnMptYOf4SPDNDhtJ7Qc3PCk6 # GJ6J/fssnK9n/3QVnAmIBSINx6vUOasQIBIvf72aGP3Ax0OMx003HDcenhkn5+YJ # 3IEMJMGN9AvoxZpNvvP2daLhVCLhtrvyPI4ZbWTmilwNQdI7KG6UQsEcVw9h+H/e # QK3GUHpgjkAQIgLlxdl2GUzuyRB7w3q8IcL2knoiXyaJnu/8ZImBAUz/e9Y0hceH # XSyLwm3yD7cTI/1NIoC3NCa4JNC0mIL34IiPmpxOsrYrnC8N56eB3RaqVTgpP0GT # A/rYkXHBAgMBAAGjggEJMIIBBTAdBgNVHQ4EFgQUEDX8qfRQm15YRy1KztfgtxHB # HfkwHwYDVR0jBBgwFoAUIzT42VJGcArtQPt2+7MrsMM1sw8wVAYDVR0fBE0wSzBJ # oEegRYZDaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv # TWljcm9zb2Z0VGltZVN0YW1wUENBLmNybDBYBggrBgEFBQcBAQRMMEowSAYIKwYB # BQUHMAKGPGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z # b2Z0VGltZVN0YW1wUENBLmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG # 9w0BAQUFAAOCAQEAUYrMwJvGAcCAGnvYWKAiGHo5ee703br1cOLmeU48bNWanQyV # B5F+9NduGCCYR+Dy/c6Qz0AAHOrfKZRMm9XVZjzR0SURkrw0XgUG+lUacr+buJk9 # soiQVq1JRSFVyzsjNTgUWRVHhIvvP9DYGG8ErZbn0b9CG4fkrmnP+K23Wdoz6PM1 # jzmLO50vGvU6WlBIVdDggAoWW4o8aomMZRdgmGxKPcNAVRVd6pvZz73GnTePE0Su # d3zOUPMLoHd+DrNbb3tOwJhCCEIs2OMvQyZ7A6sS/YlTseBH5YefOj87+ZliRZCv # hZJ/QldmfA3RI5Is2IKz45m0pmXUM9snjK0p6TCCBOswggPToAMCAQICEzMAAAF4 # JVq1zSPGX5UAAQAAAXgwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEjMCEGA1UEAxMaTWljcm9zb2Z0IENvZGUgU2ln # bmluZyBQQ0EwHhcNMTcwODExMjAxMTE1WhcNMTgwODExMjAxMTE1WjCBgjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEMMAoGA1UECxMDQU9DMR4w # HAYDVQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUA # A4IBDwAwggEKAoIBAQCZbh1TVaudsrIbXUPPB9c8S+E+dKSbskHKYlG6SGTH8jhT # hpuvGiAO87F2b9GHVN+DvszaMkGy/xQgHaGEJLfpnb3kpakic7E0bjDHdG4KnHRb # no/wfUkGLfS79o+cw//RY8Ck6yE+0czDBcxp0Gbw5JyGP+KFqvzRR/3Tv3nt/5x0 # 5ZnEOHYP+eDVikDvn/DH+oxxtiCfX3tkvtm/yX4eOb47YdmYKQjCgz2+Nil/lupY # vU0QFIjvke3jshqQINDng/vO9ys2qA0ex/q5hlLKQTST99dGoM86Ge6F723ReToq # KnGLN8kiCG7uNapOAIQrpCHZq96CVumiaA5ZvxU9AgMBAAGjggFgMIIBXDATBgNV # HSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUjuhtD3FD7tk/RKloJFX05cpgLjcw # UQYDVR0RBEowSKRGMEQxDDAKBgNVBAsTA0FPQzE0MDIGA1UEBRMrMjI5ODAzKzFh # YmY5ZTVmLWNlZDAtNDJlNi1hNjVkLWQ5MzUwOTU5ZmUwZTAfBgNVHSMEGDAWgBTL # EejK0rQWWAHJNy4zFha5TJoKHzBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js # Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNDb2RTaWdQQ0FfMDgt # MzEtMjAxMC5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY0NvZFNpZ1BDQV8wOC0zMS0y # MDEwLmNydDANBgkqhkiG9w0BAQUFAAOCAQEAYnG/oHG/xgZYR8NAMHZ/vE9GM0e4 # 7YdhuTea2uY7pSGwM707wp8Wan0Fa6evK1PWfcd/XNOh2BpEv5o8RmKDoEsG0ECP # 13Jug7cklfKreBVHQ+Djg43VVFLZpuo7aOAVK6wjlcnpPUtn+SfH9K0aM2FjDKVJ # FW6XFKXBat5R+Zp6uOxWTxpSeMTeDC5zF6IY6ogR1uzU+9EQoRlAvkwX6po+exEL # nMLr4++P+fqOxIU+PODIoB8ijClAqwwKvLlMPa3qlrNHt+LweTMu7lvGC/RA18wU # zzXAeomuZ03blUw+bkOiVgWOk4S0RN7EnW7zjJV8gd/+G2dbToUi1cB/fTCCBbww # ggOkoAMCAQICCmEzJhoAAAAAADEwDQYJKoZIhvcNAQEFBQAwXzETMBEGCgmSJomT # 8ixkARkWA2NvbTEZMBcGCgmSJomT8ixkARkWCW1pY3Jvc29mdDEtMCsGA1UEAxMk # TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTEwMDgzMTIy # MTkzMloXDTIwMDgzMTIyMjkzMloweTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEjMCEGA1UEAxMaTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0Ew # ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCycllcGTBkvx2aYCAgQpl2 # U2w+G9ZvzMvx6mv+lxYQ4N86dIMaty+gMuz/3sJCTiPVcgDbNVcKicquIEn08Gis # TUuNpb15S3GbRwfa/SXfnXWIz6pzRH/XgdvzvfI2pMlcRdyvrT3gKGiXGqelcnNW # 8ReU5P01lHKg1nZfHndFg4U4FtBzWwW6Z1KNpbJpL9oZC/6SdCnidi9U3RQwWfjS # jWL9y8lfRjFQuScT5EAwz3IpECgixzdOPaAyPZDNoTgGhVxOVoIoKgUyt0vXT2Pn # 0i1i8UU956wIAPZGoZ7RW4wmU+h6qkryRs83PDietHdcpReejcsRj1Y8wawJXwPT # AgMBAAGjggFeMIIBWjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTLEejK0rQW # WAHJNy4zFha5TJoKHzALBgNVHQ8EBAMCAYYwEgYJKwYBBAGCNxUBBAUCAwEAATAj # BgkrBgEEAYI3FQIEFgQU/dExTtMmipXhmGA7qDFvpjy82C0wGQYJKwYBBAGCNxQC # BAweCgBTAHUAYgBDAEEwHwYDVR0jBBgwFoAUDqyCYEBWJ5flJRP8KuEKU5VZ5KQw # UAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9j # cmwvcHJvZHVjdHMvbWljcm9zb2Z0cm9vdGNlcnQuY3JsMFQGCCsGAQUFBwEBBEgw # RjBEBggrBgEFBQcwAoY4aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0 # cy9NaWNyb3NvZnRSb290Q2VydC5jcnQwDQYJKoZIhvcNAQEFBQADggIBAFk5Pn8m # Rq/rb0CxMrVq6w4vbqhJ9+tfde1MOy3XQ60L/svpLTGjI8x8UJiAIV2sPS9MuqKo # VpzjcLu4tPh5tUly9z7qQX/K4QwXaculnCAt+gtQxFbNLeNK0rxw56gNogOlVuC4 # iktX8pVCnPHz7+7jhh80PLhWmvBTI4UqpIIck+KUBx3y4k74jKHK6BOlkU7IG9KP # cpUqcW2bGvgc8FPWZ8wi/1wdzaKMvSeyeWNWRKJRzfnpo1hW3ZsCRUQvX/TartSC # Mm78pJUT5Otp56miLL7IKxAOZY6Z2/Wi+hImCWU4lPF6H0q70eFW6NB4lhhcyTUW # X92THUmOLb6tNEQc7hAVGgBd3TVbIc6YxwnuhQ6MT20OE049fClInHLR82zKwexw # o1eSV32UjaAbSANa98+jZwp0pTbtLS8XyOZyNxL0b7E8Z4L5UrKNMxZlHg6K3RDe # ZPRvzkbU0xfpecQEtNP7LN8fip6sCvsTJ0Ct5PnhqX9GuwdgR2VgQE6wQuxO7bN2 # edgKNAltHIAxH+IOVN3lofvlRxCtZJj/UBYufL8FIXrilUEnacOTj5XJjdibIa4N # XJzwoq6GaIMMai27dmsAHZat8hZ79haDJLmIz2qoRzEvmtzjcT3XAH5iR9HOiMm4 # GPoOco3Boz2vAkBq/2mbluIQqBC0N1AI1sM9MIIGBzCCA++gAwIBAgIKYRZoNAAA # AAAAHDANBgkqhkiG9w0BAQUFADBfMRMwEQYKCZImiZPyLGQBGRYDY29tMRkwFwYK # CZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQDEyRNaWNyb3NvZnQgUm9vdCBD # ZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcwNDAzMTI1MzA5WhcNMjEwNDAzMTMw # MzA5WjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEwHwYD # VQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwggEiMA0GCSqGSIb3DQEBAQUA # A4IBDwAwggEKAoIBAQCfoWyx39tIkip8ay4Z4b3i48WZUSNQrc7dGE4kD+7Rp9FM # rXQwIBHrB9VUlRVJlBtCkq6YXDAm2gBr6Hu97IkHD/cOBJjwicwfyzMkh53y9Gcc # LPx754gd6udOo6HBI1PKjfpFzwnQXq/QsEIEovmmbJNn1yjcRlOwhtDlKEYuJ6yG # T1VSDOQDLPtqkJAwbofzWTCd+n7Wl7PoIZd++NIT8wi3U21StEWQn0gASkdmEScp # ZqiX5NMGgUqi+YSnEUcUCYKfhO1VeP4Bmh1QCIUAEDBG7bfeI0a7xC1Un68eeEEx # d8yb3zuDk6FhArUdDbH895uyAc4iS1T/+QXDwiALAgMBAAGjggGrMIIBpzAPBgNV # HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQjNPjZUkZwCu1A+3b7syuwwzWzDzALBgNV # HQ8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwgZgGA1UdIwSBkDCBjYAUDqyCYEBW # J5flJRP8KuEKU5VZ5KShY6RhMF8xEzARBgoJkiaJk/IsZAEZFgNjb20xGTAXBgoJ # kiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBSb290IENl # cnRpZmljYXRlIEF1dGhvcml0eYIQea0WoUqgpa1Mc1j0BxMuZTBQBgNVHR8ESTBH # MEWgQ6BBhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0 # cy9taWNyb3NvZnRyb290Y2VydC5jcmwwVAYIKwYBBQUHAQEESDBGMEQGCCsGAQUF # BzAChjhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY3Jvc29m # dFJvb3RDZXJ0LmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQUF # AAOCAgEAEJeKw1wDRDbd6bStd9vOeVFNAbEudHFbbQwTq86+e4+4LtQSooxtYrhX # AstOIBNQmd16QOJXu69YmhzhHQGGrLt48ovQ7DsB7uK+jwoFyI1I4vBTFd1Pq5Lk # 541q1YDB5pTyBi+FA+mRKiQicPv2/OR4mS4N9wficLwYTp2OawpylbihOZxnLcVR # DupiXD8WmIsgP+IHGjL5zDFKdjE9K3ILyOpwPf+FChPfwgphjvDXuBfrTot/xTUr # XqO/67x9C0J71FNyIe4wyrt4ZVxbARcKFA7S2hSY9Ty5ZlizLS/n+YWGzFFW6J1w # lGysOUzU9nm/qhh6YinvopspNAZ3GmLJPR5tH4LwC8csu89Ds+X57H2146SodDW4 # TsVxIxImdgs8UoxxWkZDFLyzs7BNZ8ifQv+AeSGAnhUwZuhCEl4ayJ4iIdBD6Svp # u/RIzCzU2DKATCYqSCRfWupW76bemZ3KOm+9gSd0BhHudiG/m4LBJ1S2sWo9iaF2 # YbRuoROmv6pH8BJv/YoybLL+31HIjCPJZr2dHYcSZAI9La9Zj7jkIeW1sMpjtHhU # BdRBLlCslLCleKuzoJZ1GtmShxN1Ii8yqAhuoFuMJb+g74TKIdbrHk/Jmu5J4PcB # ZW+JC33Iacjmbuqnl84xKf8OxVtc2E0bodj6L54/LlUWa8kTo/0xggSXMIIEkwIB # ATCBkDB5MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSMwIQYD # VQQDExpNaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQQITMwAAAXglWrXNI8ZflQAB # AAABeDAJBgUrDgMCGgUAoIGwMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG # CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBQQfPvC # lvHYL2+5g9lMlPnOoMaHIjBQBgorBgEEAYI3AgEMMUIwQKAWgBQAUABvAHcAZQBy # AFMAaABlAGwAbKEmgCRodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUG93ZXJTaGVs # bCAwDQYJKoZIhvcNAQEBBQAEggEAMqlh/UzUoXirLupZLGr5ZILpl+il4qW5TSqe # Rl/TiU4B03jnogIdZx+xFUzzfp/LoiT+0ImV0R/q30QiSwV4l6uU+2M/TNS4g41Q # pTENag1d1h96iP5t5BMWHGypY02j//ROMRSeCUFkkRAu1k6+Pdo5QSmVbobhac0C # ylVhu+7sMy/1YehFObiT/osGxWLKHBeotV+80vxFcVJ40gorhLHIRQSEctVpX7Ww # 5X9QqZH6KQBuiyIukg9waLO4ijbxMGwr+b7LO2KTmjppHvCeCyG12Jsw7nuX/Vra # O1XkUUWylVJ9zLSspd6evwHVrTZ/HSWpPHHtfW5CUKR/PQeDU6GCAigwggIkBgkq # hkiG9w0BCQYxggIVMIICEQIBATCBjjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMSEwHwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EC # EzMAAAC5vA9OV+NmZTgAAAAAALkwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzEL # BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE3MTAxMDIwNDc0OFowIwYJKoZI # hvcNAQkEMRYEFA1dgTlks9QiWTvbXrpBg/v1Mlb4MA0GCSqGSIb3DQEBBQUABIIB # AAFK1SEt+tHbsdf/FqCpH8KhzHmnHABKsmBL3OjkJhHiK6t0vldH3IPTGMX+UwQz # sZrqWes5cFUDUCVhSgZt/Ua8FNZLEwsrRQtbGTpoEN2VE+AdIW9sfOPK1wr+A22+ # tk2Qivh6Ibc18T3uGtF9RrzdLJeoBRpyHTD7xJnkQxYIefMayZbHjhuEelIybGWW # V0Mu7MQGULJbJJkibYooL+UjZzRSPE7am4DlUP4dAnPFOygssYP80p3Am7OBH9vI # BC8XSo1KtGLCjzEMNFTjRSgS4hpol9sOA7a/l94oeeLsAu7BaWVUQxZgynyMFS0s # GkmOxDEd/MG0C0dO9obYynE= # SIG # End signature block |