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 { -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] } } -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 # MIInOgYJKoZIhvcNAQcCoIInKzCCJycCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD/yENJPrBust3B # iBIhgTBKIlPshsQZzBLl9azj1MvmoaCCDMkwggYEMIID7KADAgECAhMzAAACHPrN # xZvoL37EAAAAAAIcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQxWhcNMjcwNDE1MTg1 # OTQxWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDVsZfgOKmM31HPfoWOoNEiw0SlCiIxUMC0I9NMWbucKOw/e9lP # oAoehQVu6SG65V4EPzrYsnBnFPNoi4/HoOdjhz1qkrEt4I6tEcxXU6oOeY9zGveC # /3iBeuhLYxM3M/PkcUoebF+Nednm8OkdSPoDu8imViHPQq/8CQUu0WRR4rE+dMRf # rpVqfmNi2qWCX94T4MsepijGVkwE//tJg0ryAiYdHT34LSnlG/RSBZmQRGWZ5g8j # qnKjRParSqMft1gvjuUTVgtWNZfgcLFSK5Wa0myrq8OPcgTGGsRgun+tnSS+IxDT # xVsAPH1OzvPjwomguByhUe/OcvUN0D5Wmp7xAgMBAAGjggGqMIIBpjAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFNoH7a2YDjOSwpkp6DHcmUS7J+0yMFQGA1UdEQRNMEukSTBHMS0wKwYDVQQL # EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxFjAUBgNVBAUT # DTIzMDAxMis1MDc1NjkwHwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEw # YAYDVR0fBFkwVzBVoFOgUYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w # cy9jcmwvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # bDBtBggrBgEFBQcBAQRhMF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9z # b2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmcl # MjBQQ0ElMjAyMDI0LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IC # AQAUnEqhaRXe0T3hIJjvdQErEkrA/7bByjn6t5IArODkkRjzkYwtKMc2yYj2quaN # rLutWw2YZcngKPy1b71YyDJQTy4NDRwaSh9Tw5thrk3NmcPrAHia5vtcBJ1CgtKK # 7mQbIcQ22d/N3813ayCDDFewu1+jsZmX+r/aTEqaOM4TVxVtRSkuCy8nAXKuChOK # Li/zA4XuH8iEYqIsj2YoNaeSxVmeGiERXpKdo3dDmYi0kO5w2D8VS4c3+9h6gElY # BaAAg/dYErBg27qT3vv0zRDJhJufvCNylA8S7/+8H5E/PV5cng6na9VV/w9OV3qu # uND6zdGa2EX38Glp50F9AIQk3p2xXmcvorDeM4XJ7UlWYBi6g80J1SSOQnInCYFE # msfUNn3+1AaTJKSJL83quKArTac2pKhu0Yzzzrzo6HrsRiQKzpnRBb1/dMa6P3hz # 75XbMRBctNsFhZC07WCmjExdLg2eHW5uV0TY8D5+6wozJf7vF3+WHkYPO85Z+BC6 # U4FkNbYNycZ9cE4j1tXRdyDCfml6c0HWPHjNVDObrv9lKt3qUqFpX38VCqVCyNOO # 1UcXfQiVjJw32U2WUKZjt/neJKHEBsm9kFsLuWzkQ53+qcaSaytmsCnk2gOglrlD # 5d3kKyvvAw+rzm0lT8K38P6PLxfZQHhu4W8dV7Av8N2ZmDCCBr0wggSloAMCAQIC # EzMAAAA5O7Y3Gb8GHWcAAAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoX # DTM2MDMyMjIyMTMwNFowVzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQ # Q0EgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeq # lRYHNa265v4IY9fH8TKhemHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo # 0dtS/EW6I/yEL/bLSY8hKpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATv # QVL4tcf03aTycsz8QeCdM0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a # 1uv1zerOYMnsneRRwCbpyW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1 # FyQfK0fVkaya8SmVHQ/tOf23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfO # GSWHIIV4YrTJTT6PNty5REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7 # ttOu1bVnXfHaqPYl2rPs20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJ # uz2MXMCt7iw7lFPG9LXKGjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxS # CwyoGIq0PhaA7Y+VPct5pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOm # VQop36wUVUYklUy++vDWeEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3 # SkE/xIkgpfl22MM1itkZ35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8E # BAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPX # LQaUEggxMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMB # Af8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBP # oE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAw # TgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOC # AgEAFJQfOChP7onn6fLIMKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D # 5W4wMwYeLystcEqfkjz4NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBY # nbu0+THSuVHTe0VTTPVhily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSI # vgn0JksVBVMYVI5QFu/qhnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6 # aR9y34aiM1qmxaxBi6OUnyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4w # PKC5OmHm1DQIt/MNokbbH3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7 # RTX8AdBPo0I6OEojf39zuFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK # /fg8B2qjW88MT/WF5V5uvZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSK # YBv0VisCzfxgeU+dquXW9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkw # YTu/9dLeH2pDqeJZAABVDWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVT # Ql0v4q8J/AUmQN5W4n101cY2L4A7GTQG1h32HHAvfQESWP0xghnHMIIZwwIBATBu # MFcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc # +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwG # CisGAQQBgjcCAQQwLwYJKoZIhvcNAQkEMSIEIH3kfcA4wX5En83Y74qj6xLpNk3Y # zNvusM+UGHkrJdCSMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBv # AGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAE # ggEArrCZGIZo4G3XSbWy4BRhxnUciQH2F5lkor3dDI7mVCaxLEqL2g2sVG89/H0d # ZuyxepTshGJLwz0DWF0EkMJjEve4p2p6JJGnEbhrNaZlOnppbjC9qUzYLGLeEwTH # kFVzQfAczcUErXdhK1eCfXtpfx/NTNzqxM/oMg8cStlHUxCWv8MmkLzKw2kQHhpm # GHbZ0L5NnNvA84pmDc3W/KQdpnBJoC5DzrdyZe8eN7NRwx6Y0V3hFnJphdcASRDv # o+1Fjc4KnrNh6I2pLgDVtZ7D2nSJhybLzN72yEH6xsxF5GWvdyyqEU49TsrKrUZ4 # Qz7bnrdWMogychdvpgHhJzAawKGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCCF38G # CSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsqhkiG # 9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQC # AQUABCD2++kMSZYscw/pvg8wI77opQNEJoXnFyIlqfi+QEJ4XwIGater4TNxGBMy # MDI2MTAyNTExMjg0NC41ODNaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj # YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0wNUUw # LUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghHt # MIIHIDCCBQigAwIBAgITMwAAAiu7AFD/TTuaoQABAAACKzANBgkqhkiG9w0BAQsF # ADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNjAyMTkxOTQwMTFa # Fw0yNzA1MTcxOTQwMTFaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScw # JQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0wNUUwLUQ5NDcxJTAjBgNVBAMT # HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCX3mi6OD3syUqQm4QqgkrKPbcsK/Qx3fYctL8+VM1uOY3b # ooi5GxwauTgQf6JFHITToxS7gjqKlK8OFLzL6UTl0jxEK5t6DuOcgJXdvutimoTl # OS0C3kyITXBAXoj/gp6hRR9z6WRip1Ktkilb3dJXCjQqT9P2Cuujr+Vz8r+Z+jDl # 09ji/ic/4G34r3mVwjs//Gnx9Pu31V8rXFicNiAzxpubawpbd8pqfzlWT2vnG3kF # 9l6MiREbvJ3XHLUwHQsh0t/TrSFx/s/yCqpJWYJ6oClG70tvsFH0aRP8wB4cP/CF # a2ILvk26i3OcJBl+pqKjHTSBy9mvwTPEDlnzco0Nt8R6pSPTXZgBsscHhoKfC0WQ # mOzY2keXbAmRTcZMyXz5v/AJbmoI0y07Bazvt5NkXddG9TErQWwtsFyIKrElDgWf # HeCoTu1wu2ciD3dK72z3ca2gzoEDxT2j9BXIUKaiTzTdQPRsAMaO3dU0zaGwMMlw # tSJyDh14YEgZoUu5vS8MugMqdrNjphyL65yKhjpAWbhYkIHO/0uZju95tP8zZNqX # IRh4tdfWHJPATn9r+cxkyuh2x0VLdfx1lmK9X3NjH0NtgAs5JB/wOlkyuudxmFTf # WVyRrL37ispOZ8aPAFgvyR6cNTkGpkFo35JRjciNmZiU4qT9Uty+V5gudFk1jwID # AQABo4IBSTCCAUUwHQYDVR0OBBYEFD4WjuQTUJbtbd3jmvZku0FZ2eU2MB8GA1Ud # IwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRp # bWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYI # KwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMv # TWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1Ud # EwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeA # MA0GCSqGSIb3DQEBCwUAA4ICAQDO/CKsciEM8kr1fqH4TlfT66ENoTjxXw810pyE # q0PdrgLwfgT3x+1gz7CQHtUdevqMQ5qHyDLhm6pT911CYkGN+6g+MU7fMYTr6d3S # xieJwBIoWkfR4g7SitGzMKU465KEYejfddoUgovC/xcRpaALO5p3/A248ByhJiMt # tBQNDtsT/HaCFwRFCURby/f8c1kky8F8xkCXFz+/MtZ5d1lWFjwOI2geZHWq9Xih # DOgee5nS2koo5V6n8XG220UTevVf+pgmpIH71XKDVIYTGGZJs6yPlfJ2aXqw1ME4 # NR6okNsY3P1M31H6DMYRfJGNBNep595kXGh3YzA3cCiyg+jmJ58h/fTvjngIpuUF # fODpDjFx0ic1YoLANxhCF3RhS9qYM7K40NEhKshYuaAkIG2XBKYig3r/0/b0sjvj # Bws55AYonMm3A8qcX/6k9Vfc0mv9dtonHuWGfA2b+qE2qpCnhzGbdDHq7iOSZEw0 # 1nNupAMf1c41k9IoTQ2z3iw6w4ZZoLOyg4TKMbp1krpT4trip/y30Cv5khyqCDNq # aXQpBkOYON8LgtoQ3amVOX7ix5jdrnx/vUxTUSigXvrWdL7Uk8kpmS0zto2Toy7a # T5oBzCTvfj9iJ/BN/E1vhFBkhJCvZ7PVvsMSnTTmkx2Fal2lVkztuAI44fD/uyLJ # daMQSzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcN # AQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAw # BgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEw # MB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt # U3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk # 4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9c # T8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWG # UNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6Gnsz # rYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2 # LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLV # wIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTd # EonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0 # gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFph # AXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJ # YfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXb # GjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJ # KwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnP # EP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMw # UQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9z # b2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggr # BgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYw # DwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoY # xDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtp # L2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYB # BQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v # cGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0B # AQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U5 # 18JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgAD # sAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo # 32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZ # iefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZK # PmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RI # LLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgk # ujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9 # af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzba # ukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/ # OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQMIIC # OAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUG # A1UECxMeblNoaWVsZCBUU1MgRVNOOkEwMDAtMDVFMC1EOTQ3MSUwIwYDVQQDExxN # aWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQAJrD90 # ykHpo/0AGb7lmwvsCtqROaCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7ogXzjAiGA8yMDI2MTAyNTA1NTMxOFoY # DzIwMjYxMDI2MDU1MzE4WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDuiBfOAgEA # MAoCAQACAiPzAgH/MAcCAQACAhhAMAoCBQDuiWlOAgEAMDYGCisGAQQBhFkKBAIx # KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI # hvcNAQELBQADggEBAANNdKB25fHafT7LsGwKRnbQ84Jp1+S5kY3/O43Qw2NM1Irx # 08OPtM1P8QqfrHqR/4DH7R7JMXt/ZJ3SNBHLvf/r7G8JCr5YZ5L4nbNptjVqUxvd # GHGisVergWCUwge0aiEP/grgixuf5J3xQGrFZNHDUj5zaiJfva2l7DXK+KXjvLWr # aXegZSqVZw10JFggpBynkEZqjrWT/X1men+bQYPInjbBMMM32yksk1B4XbKUYrb0 # 2BQeB3bvkh7yWLZKrpcBHEooFDLqM2j0foVheT1nJeLai2oVeiBneT0qnguBJqbR # RyBc13tViS9GnUSp0r9pkd9wbYGZKG4Opbc+KrkxggQNMIIECQIBATCBkzB8MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy # b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAiu7AFD/TTuaoQABAAACKzAN # BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G # CSqGSIb3DQEJBDEiBCBPRt+zV9MHDZhSQMor4N/7ol5zGx9WCbwrn0vimFPCQDCB # +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIHIOI/Q/kFftYA+M2OY+1Bx3ajBD # 6/WDAtPT2vFkv25SMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTACEzMAAAIruwBQ/007mqEAAQAAAiswIgQgrg0D1V3Doto4L+XEAC2rYy+CR6VK # zZF3/pPqt+lyCvswDQYJKoZIhvcNAQELBQAEggIANIWUZEOYqET1/w/AdG2NKg3r # Wz5bnRtxgEvS97dYdBIFiaUHPNy8OLFGCp1+0ZeJ/wxJGolXe2baFpUcm0gzuJxR # plvKjPEuze+iMso7DT+LglvHnjAmuWIagIdigU9rudaeWWVPceGUhnCgKf3dtGH7 # mXrtrVgIxpSkPBZMD6GYBlGXaH9iPEiK5nvA8Jf09bRakidBnhu9jcguN8YdX6M8 # 3Qwaf6iZJXHkST3dtWfGUlG8RvUtz+ZPHCpBX/kvlVrks5Y5VsJOOjgiUNsWYzyj # 5xhYu5bpYiz4JAO4tqO4jTqHSLQUK14zmNgoVbz7qWuC8wsTOV5RnRjz+29+reJ0 # jJFWG/0pkk2QEVjvPQrkv8YqPQvcuuQ5IXrEmsEzyen1yzujI33nGps7Rdq5aCEG # JIHHmPTw/LDkuIL2kNtbOwlEoJP2WpHdPW7rlMtA4J273z0RgGLQ3l32xiGEsPdh # As/Cxbf1L8UNws9W3gibKi71ayPkFDYLKhHKJbjH5Abp+nAAp+lbP0k8GEIemWol # WlupX0qJv4ysoLDQ/AhHRHgf10gbagGPRvPBeq/BCXVk9NivuAjbpk6NpS+PvmJb # 15qrjsS6mwP+E7dM5OYQ+zmVfawXE2vT6n6/msOly9Ibijp276POD1fobttWtQeg # vR2fBTce3ndu5YGJI4E= # SIG # End signature block |