Private/Tests/Cleanup/Invoke-AvmBicepCleanup.ps1
|
function Invoke-AvmBicepCleanup { [CmdletBinding(SupportsShouldProcess)] [OutputType([pscustomobject])] param( [Parameter(Mandatory)] [string] $StatePath, [Parameter(Mandatory)] [guid] $SubscriptionId, [Parameter(Mandatory)] [guid] $TenantId, [ValidateRange(1, 1000)] [int] $SearchRetryLimit = 40, [ValidateRange(0, 3600)] [int] $SearchRetryInterval = 60, [ValidateRange(1, 100)] [int] $RemovalRetryLimit = 3, [ValidateRange(0, 3600)] [int] $RemovalRetryInterval = 15, [switch] $RequireCompleteRemoval ) Set-StrictMode -Version 3.0 $ownerTag = (Get-AvmBicepConfiguration)['e2e']['ownershipTag'] $ErrorActionPreference = 'Stop' $StatePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($StatePath) $state = Read-AvmBicepCleanupState -Path $StatePath if ($state['subscriptionId'] -ine $SubscriptionId.ToString('D') -or $state['tenantId'] -ine $TenantId.ToString('D')) { throw [AvmConfigurationException]::new('Cleanup state does not match the explicitly selected subscription and tenant.') } if ($RequireCompleteRemoval -and @($state['deployments'] | Where-Object { $_.Contains('recordDeletion') }).Count -gt 0) { throw [AvmConfigurationException]::new('Recorded deployment deletion progress is for cleanup-only recovery, not deployment replay.') } if (-not $PSCmdlet.ShouldProcess( "recorded Bicep deployments in $SubscriptionId, tenant $TenantId", 'Resume deployment-owned cleanup')) { return [pscustomobject]@{ Cleaned = $false; Status = 'skipped'; Pending = @(); Issues = @(); StatePath = $StatePath } } if ($state['status'] -ceq 'Complete') { return [pscustomobject]@{ Cleaned = $true; Status = 'pass'; Pending = @(); Issues = @(); StatePath = $StatePath } } Assert-AvmBicepAzureDependency $az = Get-Command -Name 'az' -CommandType Application -ErrorAction Stop | Select-Object -First 1 $discoveryOptions = @{ SearchRetryLimit = $SearchRetryLimit SearchRetryInterval = $SearchRetryInterval RequireCompleteRemoval = $RequireCompleteRemoval IsolationState = $state } $removalOptions = @{ RetryLimit = $RemovalRetryLimit RetryInterval = $RemovalRetryInterval RequireCompleteRemoval = $RequireCompleteRemoval } Invoke-AvmBicepAzureContext -SubscriptionId $SubscriptionId -TenantId $TenantId -ScriptBlock { $context = Get-AzContext -ErrorAction Stop $environment = Get-AvmPropertyValue -InputObject $context -Name 'Environment' if ((Get-AvmPropertyValue -InputObject $environment -Name 'Name') -cne $state['environment']) { throw [AvmConfigurationException]::new('Cleanup state belongs to a different Azure cloud.') } Assert-AvmBicepAzureIdentity -AzPath $az.Source -SubscriptionId $SubscriptionId -TenantId $TenantId $state['status'] = 'CleanupPending' Save-AvmBicepCleanupState -State $state -Path $StatePath -Confirm:$false $readiness = Get-AvmBicepPendingDeployment -State $state ` -RetryLimit $discoveryOptions.SearchRetryLimit -RetryInterval $discoveryOptions.SearchRetryInterval Save-AvmBicepCleanupState -State $state -Path $StatePath -Confirm:$false if ($readiness.Pending.Count -gt 0) { foreach ($issue in $readiness.Issues) { Write-AvmLog -Level Warning -Message $issue.Message } return [pscustomobject]@{ Cleaned = $false; Status = 'fail'; Pending = $readiness.Pending Issues = $readiness.Issues; StatePath = $StatePath } } $issues = [System.Collections.Generic.List[object]]::new() $saveDeletionProgress = { param($RecordId, $RecordStatus) foreach ($owner in $state['deployments']) { if ($owner['id'] -ieq $RecordId) { $owner['recordDeletion'] = $RecordStatus Save-AvmBicepCleanupState -State $state -Path $StatePath -Confirm:$false break } } } foreach ($owner in @($state['deployments'] | Where-Object { $_['recordDeletion'] -ceq 'Pending' })) { try { Remove-AvmBicepDeploymentRecord -DeploymentIds @($owner['id']) -ConfirmOnly ` -RetryLimit $RemovalRetryLimit -RetryInterval $RemovalRetryInterval -OnProgress $saveDeletionProgress -Confirm:$false } catch { if ((Get-AvmBicepDeploymentErrorKind -ErrorRecord $_) -eq 'Cancellation') { throw } $issues.Add([pscustomobject]@{ ResourceId = $owner['id']; Code = 'DeploymentRecordRetained'; Message = $_.Exception.Message }) Write-AvmLog -Message $_.Exception.Message -Level Warning } } $ids = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $blockedGroups = [System.Collections.Generic.List[string]]::new() $discoverOwners = @($state['deployments'] | Where-Object { $_['status'] -cne 'NotSubmitted' -and -not $_.Contains('recordDeletion') }) $discovery = Get-AvmBicepDeploymentCleanupTarget @discoveryOptions ` -DeploymentIds @($discoverOwners | ForEach-Object { $_['id'] }) ` -PreflightRejectedDeploymentIds @($discoverOwners | Where-Object { $_['preflightRejected'] } | ForEach-Object { $_['id'] }) foreach ($issue in $discovery.Issues) { $issues.Add($issue) } foreach ($id in $discovery.ResourceIds) { $null = $ids.Add($id) } foreach ($owned in $state['ownedResourceGroups']) { $groupSubscription = $owned['id'].Split('/')[2] try { $group = Invoke-AvmBicepAzureContext -SubscriptionId $groupSubscription -TenantId $TenantId -ScriptBlock { Invoke-AvmBicepCleanupLookup -Command 'Get-AzResourceGroup' -Parameters @{ Name = $owned['id'].Split('/')[-1] } -RetryAuthorizationPropagation } -Confirm:$false if ($null -ne $group) { $actualId = Get-AvmPropertyValue -InputObject $group -Name 'ResourceId' $tags = Get-AvmPropertyValue -InputObject $group -Name 'Tags' if ($actualId -ine $owned['id'] -or (Get-AvmPropertyValue -InputObject $tags -Name $ownerTag) -cne $owned['runId']) { throw [AvmProcessException]::new("Ownership could not be confirmed for '$($owned['id'])'.") } } $null = $ids.Add($owned['id']) } catch { if ((Get-AvmBicepDeploymentErrorKind -ErrorRecord $_) -eq 'Cancellation' -or $_.FullyQualifiedErrorId.Split(',')[0] -eq 'AvmBicepContextRestoreFailed') { throw } $blockedGroups.Add($owned['id']) $issues.Add([pscustomobject]@{ ResourceId = $owned['id']; Code = 'GroupOwnershipUnverified'; Message = $_.Exception.Message }) Write-AvmLog -Message $_.Exception.Message -Level Warning } } $records = [System.Collections.Generic.Dictionary[string, object]]::new( [System.StringComparer]::OrdinalIgnoreCase) foreach ($resource in Get-AvmBicepCleanupResourceRecord -Existing $state['resources'] -ResourceIds @($ids)) { $records.Add($resource['id'], $resource) } $excluded = @($records.Keys | Where-Object { Test-AvmBicepCleanupExclusion -ResourceId $_ -SubscriptionId $state['subscriptionId'] }) foreach ($id in $excluded) { if ($RequireCompleteRemoval) { $issues.Add([pscustomobject]@{ ResourceId = $id Code = 'ProtectedResourceRetained' Message = "Workflow-excluded resource would remain after cleanup: $id" }) } Write-AvmLog -Message "Retaining workflow-excluded resource '$id'." -Level Info $null = $records.Remove($id) } $state['resources'] = @($records.Values) $blocked = [System.Collections.Generic.List[string]]::new() foreach ($resource in $state['resources']) { if ($discovery.Issues.Count -gt 0 -and $resource['type'] -in @('Microsoft.Resources/resourceGroups', 'Microsoft.Management/managementGroups')) { $blocked.Add($resource['id']) } if ($state.Contains('case') -and $state['case']['scope'] -eq 'mg' -and $resource['id'] -ieq ('/providers/Microsoft.Management/managementGroups/' + $state['case']['managementGroupId'])) { $blocked.Add($resource['id']) $issues.Add([pscustomobject]@{ ResourceId = $resource['id']; Code = 'ExecutionTargetProtected' Message = 'The existing management group selected as the execution target cannot be removed.' }) } foreach ($groupId in $blockedGroups) { if ($resource['id'] -ieq $groupId -or $resource['id'].StartsWith( $groupId + '/', [System.StringComparison]::OrdinalIgnoreCase)) { $blocked.Add($resource['id']) break } } } Save-AvmBicepCleanupState -State $state -Path $StatePath -Confirm:$false $batch = Remove-AvmBicepCleanupResourceBatch -State $state -StatePath $StatePath ` -BlockedResourceIds $blocked.ToArray() @removalOptions -Confirm:$false foreach ($issue in $batch.Issues) { $issues.Add($issue) } if ($RequireCompleteRemoval -and $batch.Cleaned -and $issues.Count -eq 0) { $recordIds = @($discovery.Deployments | Where-Object { $_.Status -eq 'Resolved' } | Sort-Object -Property @{ Expression = { $_.Depth }; Descending = $true } | ForEach-Object { $_.Id }) $removedIds = @($state['resources'] | Where-Object { $_['removed'] } | ForEach-Object { $_['id'] }) try { Remove-AvmBicepDeploymentRecord -DeploymentIds $recordIds -RemovedParentIds $removedIds ` -RetryLimit $RemovalRetryLimit -RetryInterval $RemovalRetryInterval -OnProgress $saveDeletionProgress -Confirm:$false } catch { if ((Get-AvmBicepDeploymentErrorKind -ErrorRecord $_) -eq 'Cancellation') { throw } $issues.Add([pscustomobject]@{ ResourceId = ''; Code = 'DeploymentRecordRetained'; Message = $_.Exception.Message }) Write-AvmLog -Message $_.Exception.Message -Level Warning } } $cleaned = $batch.Cleaned -and $issues.Count -eq 0 $state['status'] = if ($cleaned) { 'Complete' } else { 'CleanupPending' } Save-AvmBicepCleanupState -State $state -Path $StatePath -Confirm:$false [pscustomobject]@{ Cleaned = $cleaned Status = if ($cleaned) { 'pass' } else { 'fail' } Pending = @( @($batch.Pending) + @($blockedGroups) + @($state['deployments'] | Where-Object { $_['recordDeletion'] -ceq 'Pending' } | ForEach-Object { $_['id'] }) + @($discovery.Issues | ForEach-Object { $_.DeploymentId }) | Select-Object -Unique ) Issues = $issues.ToArray() StatePath = $StatePath } } -Confirm:$false } # SIG # Begin signature block # MIInQQYJKoZIhvcNAQcCoIInMjCCJy4CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCCrJjw6C2Jgnem # AtsmhDWZnsSjihwzlfQNTzz1QCNWDqCCDLowggX1MIID3aADAgECAhMzAAACHU0Z # yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1 # OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8 # o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg # 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4 # Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R # X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk # ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw # HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg # UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh # MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9 # s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H # VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3 # w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n # 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs # A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo # Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb # SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6 # 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z # V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v # 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs # /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA # AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow # VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo # MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh # emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h # KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd # M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp # yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t # Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5 # REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs # 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK # Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5 # pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW # eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ # 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC # NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI # MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4 # NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh # ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q # hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU # nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb # H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z # uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u # vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW # 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV # DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10 # 1cY2L4A7GTQG1h32HHAvfQESWP0xghndMIIZ2QIBATBuMFcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv # c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w # DQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwLwYJ # KoZIhvcNAQkEMSIEIDbyM2Ee1zdER6bW5aWU27YANOUmRPXmMI+CLos3QSp9MEIG # CisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAkJ2Q7BMQILvDV6Ok # W56CPJLjB0xdfWtNjqzj4kCMHGf+z2suFGKaUa6YJReVU6Bsi5kobCXTFCiV0A0I # s1P5qqTMi+zKE7V1wr3goa3iEcooqlGGSeARZnRmK0XqML8MEC+/epDcEd9lQsIT # +3O0Ih6EA0/62SFL0rMZEIsz9poj7QFhNC3uwxWgcSdmHZcnMeS5xhpFe34GzXjk # UvkVS2X+186qroMXxEw7ATcM4AZZNc1ceHfLysT8WBbzTeXrQ24Y7Syb4JAhm3L7 # ZveijxzzwvPQrUihbytzypAE91iUWvgH4PydGrI3cb8qNvg89F0A6//S6tPbBIsL # J2NyLKGCF60wghepBgorBgEEAYI3AwMBMYIXmTCCF5UGCSqGSIb3DQEHAqCCF4Yw # gheCAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFF # MIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCDtjLP1d1RhwcZt # AbKzz8eUgFKWHShbFebLyvxu9oLhbAIGauveSVXCGBMyMDI2MTEwOTIxMTYzMi42 # ODRaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExp # bWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo1NTFBLTA1RTAtRDk0NzEl # MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEfswggcoMIIF # EKADAgECAhMzAAACMD8HBDPE+znWAAEAAAIwMA0GCSqGSIb3DQEBCwUAMHwxCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv # c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI2MTAxNTE4MzczNVoXDTI4MDEx # MTE4MzczNVowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # LTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjU1MUEtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA1cEQVtstXsck0ADRFt9EV4LZG44m5xoRjTIHfdKjXBeh # 22TDpVY5NvxQBdQuIgcf/aeXwnQop/membLFvCsLxEgK3Yz2RXe/7DRO1IR7d85o # FNqqb/uEqVPtPR5kaopbbfwm8meA8B6dRNEzSevc5CO5kJ8Z7YBWiwMtsqVIIzpW # YotsmgbsYasFgVSWHu4NMFHOj5J2KW8d2vO7YwAGr2JSvZ3M9FimfT/4rDyvl+Jz # DMQ+ud/Qun6mMe4nX4OeP6CFQOutNB5W0BBxQOxgcFaWmV/ZDwDzk3s2BPhuw7y8 # E9Pk8EtKvUL24SG8J2uShrJ1+sDaJJIwXCSauQVydT/IzdXk1d2U1rtdS/mjJR1E # iKCfAUfcuswSZdlqhbJO2DG2hU6b9+JD3C3ndKJOzQuClIXPXAcXMJ1q3BLHvHRA # yTzk9dI1+isryfgtf+Ai/tfJGIxpTYRFRUZv3UuxtRkbY/f1dTJjtkgBYqqZAWXh # 7zmeAGOD+hopnWF3n2YBQaB++vwW15PgIs31PqlyYjhIyzZsWGR9/UHm0GWT1uFw # cOohQ1amSf6KTiWbKM2GJJWPlV89OS4L2nGlTmmc6kBOQR5l7n6oB5tH/q/SA4j6 # htrx6ofVaOXl3engKmq1h30Dkj9kr66chuqV+OOWjK3UNZsymIvRCIZKLMNVCHkC # AwEAAaOCAUkwggFFMB0GA1UdDgQWBBTiqGFDQRnAqqVRJAjMxZuO23GwBTAfBgNV # HSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5o # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBU # aW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG # CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRz # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNV # HRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIH # gDANBgkqhkiG9w0BAQsFAAOCAgEAAdh3Fu8eRSaaR7qC4Zd/DX+PAyhQUAV6/giV # CUGlkpxu0qJ8cQvrd+gDQ0xlZssBuPqwiHv9N8YV1DF8s4s4jn2oI1Sv9Tw6jOnq # qQux6GVfd9P0vSCU1R2dn8FnQkDjDa4VifGPCw+K7s4yutEWL5CDwWI4w5TJR76Y # VS386EZoO+r4Abdo4EBcCNMGxei7CUp5wzMEK1X+lr5h/yNMeigLnxsCT4NiySCX # Z9xRCBRniaeHlLL0VwjdUyK1TbZYOnB2Gfly8ILL+AYbeYXVJuilNV5ToQcJzrXr # U3a7eO+PnkMtcetAHskjDlJMga6ZbnB4kvwl4ZVDx9gYk+iArYhTGS9jd45Md8Dd # XhNA/Ep2vT8HexrieDz2fh6JM3h/UFaBPvcVH+sCWPPpKKzX9TGlPfPamYO+cjyk # uHfD/eryV8MZGtJ/5o+lfdMZf4mHzRF4q5U+JR6NiNauGi6+pAoRgMVnuSdC1X8w # ZEXHqeqx6h82GNn/m7J0O35UnXkBfNXat22oCr9RRmzLu+OKscbKa/Jfc+EeLKHX # gfj4s+MDVEP4bQUFODN22SAVVP7YCmmwDoX8+u7wevaGEFeDbh+n5O6FIRoGOxSc # T96C0SvhtdMMyzD9dDnJtuDZYyXWU+i0qFQR3WHwsd1N84ESx5QU8OstdLQau0Sp # pGMY88AwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3 # DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIw # MAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAx # MDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l # LVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA # 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/ # XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1 # hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7 # M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3K # Ni1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy # 1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF80 # 3RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQc # NIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahha # YQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkL # iWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV # 2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG # CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUp # zxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBT # MFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYI # KwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG # MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186a # GMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br # aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsG # AQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcN # AQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1 # OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYA # A7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbz # aN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6L # GYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3m # Sj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0 # SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxko # JLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFm # PWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC482 # 2rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7 # vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDVjCC # Aj4CAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExp # bWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo1NTFBLTA1RTAtRDk0NzEl # MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsO # AwIaAxUAp7M4L4fH9llHoq6sJyqTIY6+wT6ggYMwgYCkfjB8MQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt # ZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO6cSwkwIhgPMjAyNjEx # MDkxMzM3MTNaGA8yMDI2MTExMDEzMzcxM1owdDA6BgorBgEEAYRZCgQBMSwwKjAK # AgUA7pxLCQIBADAHAgEAAgIp9jAHAgEAAgISlzAKAgUA7p2ciQIBADA2BgorBgEE # AYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYag # MA0GCSqGSIb3DQEBCwUAA4IBAQCY36xX3H18ydfTfzeR7juK6URWbOI/XojeEqnP # 1X88zXDhTDcIodDNGo18rVYqrxgfCng3zRT/zIGCQOrbrPPwxyCV5wBCVCv0j+zX # M/3tkhj44kvPoalm8zHk+jB1VlbW5hwBgOuq6TediFkTrNkL4WWDcIB5+Tp+yAw1 # +oyPKCeLUCvdRLjNw+M3oV/FeuNfLRN2nJAOTZ+AaKgBTWKNVT4LPSzRDIoYazgs # ynbHI+H+jxIfQXyzLpkf9aFpv6zspw+q3LeSW6OyZIFWWItvZO+SFRdVKzxdKR/B # eWw6Uzdb/MCz1iqA5N/CDc7nFjHcBGCHLmWT4Cuyy32BUl+MMYIEDTCCBAkCAQEw # gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIwPwcEM8T7OdYA # AQAAAjAwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B # CRABBDAvBgkqhkiG9w0BCQQxIgQg0EnLlTFOPG2ydbtudETCNu6/juBB1qNNuetO # zZPUf/UwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAUkli670WWz4FWuHPG # aXjpzlNyR2AbvTse3KmiAh/RaDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwAhMzAAACMD8HBDPE+znWAAEAAAIwMCIEIGFZZxie6IBC79JaDftC # m0MwoK8v3TCakRbKvO7GInDSMA0GCSqGSIb3DQEBCwUABIICADprTv6YTMRw6yKQ # RRl4uNqajxclVxbl1fifOLBy22JuOVwWGoT2kV603mhCwMqUmUBws81buD9YvwU4 # bMWiuJw9RA50xI+m1lIr8iIAdZ4XYjk5XIrVADVnKVoZJ2y3xTbsWI7FC7AExYrQ # SD1GHRgq8VbmSoD9EfalsknASewOCQnXJZt8TgG+gC+zDgl8GAC1tU5/CQlNfCnH # kd/S+HxWW1L3gViTuKRZ8WLiTURpGx9ODHiI12IsKmMas+3NQyaRBVZXicszIGnf # nbTjSBdfVXyCG5mmirUYsF8W9sCgV6QJHa4k4FLXQ+0IGe70OehmSBf72XBgZBrw # Qq4c/kW3gA6b/6ANd3qQyP/+/J5wvySE4hku3qlkNr5+hC5aL7X6Fc8eTHm3M0xR # RbDDxHzk8cCBfyexh80JveOsvsh3b8uEvVqYi/L3wq69gpa1hog3gamCt9OXtQLM # YEunQRo5KOvoDwG6JnESC370KEsWjA9JnfW02k2rnGTJlrc/muoBh/c0I5jpUGRa # zLq+/aBrZKNd0dZzedJhq2eOt7rrsI/71x7sTXhpwO+HSPCBzNvyclwXTQsQI38I # gIKDTBYn++N6oZWtxrxcIuFkY0HRQyUuDxAzvMjuj5DjWpHFvxumIxMTlVBd1lXi # xmwYQ43xTcRXO/hM+TJLj5f0MbwV # SIG # End signature block |