Public/Remove-GitHubAccelerator.ps1
|
function Remove-GitHubAccelerator { <# .SYNOPSIS Removes GitHub resources created by the Azure Landing Zone accelerator bootstrap modules. .DESCRIPTION The Remove-GitHubAccelerator function performs cleanup of GitHub resources that were created by the Azure Landing Zone accelerator bootstrap process (https://github.com/Azure/accelerator-bootstrap-modules). This includes repositories, teams, and optionally runner groups. The function operates in the following sequence: 1. Validates GitHub CLI authentication 2. Prompts for confirmation (unless bypassed or in plan mode) 3. Discovers and deletes repositories matching the specified patterns 4. Discovers and deletes teams matching the specified patterns 5. Optionally discovers and deletes runner groups matching the specified patterns CRITICAL WARNING: This is a highly destructive operation that will permanently delete GitHub resources. Use with extreme caution and ensure you have appropriate backups and authorization before executing. .PARAMETER Organization The GitHub organization name where the resources to be deleted are located. This parameter is required. .PARAMETER RepositoryNamePatterns An array of regex patterns to match against repository names. Repositories matching any of these patterns will be deleted. If empty, no repositories will be deleted. Default: Empty array (no repositories deleted) .PARAMETER TeamNamePatterns An array of regex patterns to match against team names. Teams matching any of these patterns will be deleted. If empty, no teams will be deleted. Default: Empty array (no teams deleted) .PARAMETER RunnerGroupNamePatterns An array of regex patterns to match against runner group names. Runner groups matching any of these patterns will be deleted. If empty, no runner groups will be deleted. Default: Empty array (no runner groups deleted) .PARAMETER BypassConfirmation A switch parameter that bypasses the interactive confirmation prompts. When specified, the function waits for the duration specified in -BypassConfirmationTimeoutSeconds before proceeding, allowing time to cancel. During this timeout, pressing any key will cancel the operation. WARNING: Use this parameter with extreme caution as it reduces safety checks. Default: $false (confirmation required) .PARAMETER BypassConfirmationTimeoutSeconds The number of seconds to wait before proceeding when -BypassConfirmation is used. During this timeout, pressing any key will cancel the operation. This provides a safety window to prevent accidental deletions. Default: 30 seconds .PARAMETER ThrottleLimit The maximum number of parallel operations to execute simultaneously. This controls the degree of parallelism when processing resources. Higher values may improve performance but increase API throttling risk. Default: 11 .PARAMETER PlanMode A switch parameter that enables "dry run" mode. When specified, the function displays what actions would be taken without actually making any changes. This is useful for validating the scope of operations before executing the actual cleanup. Default: $false (execute actual deletions) .EXAMPLE Remove-GitHubAccelerator -Organization "my-org" -RepositoryNamePatterns @("^alz-.*") -PlanMode Shows what repositories matching the pattern "^alz-.*" would be deleted from the "my-org" organization without making any changes. .EXAMPLE Remove-GitHubAccelerator -Organization "my-org" -RepositoryNamePatterns @("^alz-.*") -TeamNamePatterns @("^alz-.*") Deletes all repositories and teams matching the pattern "^alz-.*" from the "my-org" organization. .EXAMPLE Remove-GitHubAccelerator -Organization "my-org" -RepositoryNamePatterns @("^alz-.*", "^landing-zone-.*") -RunnerGroupNamePatterns @("^alz-.*") Deletes repositories matching either pattern and runner groups matching "^alz-.*" from the "my-org" organization. .EXAMPLE Remove-GitHubAccelerator -Organization "my-org" -RepositoryNamePatterns @("^test-alz$") -BypassConfirmation -BypassConfirmationTimeoutSeconds 10 Deletes the repository named exactly "test-alz" with a 10-second confirmation bypass timeout. .NOTES This function requires the GitHub CLI (gh) to be installed and authenticated. Install GitHub CLI: https://cli.github.com/ Authenticate: gh auth login Required permissions: - delete:repo (to delete repositories) - admin:org (to delete teams and runner groups) #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, HelpMessage = "[REQUIRED] The GitHub organization name.")] [Alias("org", "GitHubOrganization")] [string]$Organization, [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Regex patterns to match repository names for deletion.")] [Alias("repos")] [string[]]$RepositoryNamePatterns = @(), [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Regex patterns to match team names for deletion.")] [Alias("teams")] [string[]]$TeamNamePatterns = @(), [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Regex patterns to match runner group names for deletion.")] [Alias("runners")] [string[]]$RunnerGroupNamePatterns = @(), [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Bypass interactive confirmation prompts.")] [switch]$BypassConfirmation, [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Seconds to wait when bypassing confirmation.")] [int]$BypassConfirmationTimeoutSeconds = 30, [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Maximum parallel operations.")] [int]$ThrottleLimit = 11, [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Enable dry run mode - no changes will be made.")] [switch]$PlanMode ) # Main execution starts here if ($PSCmdlet.ShouldProcess("Delete GitHub Resources", "delete")) { Test-Tooling -Checks @("GitHubCli") $TempLogFileForPlan = "" if($PlanMode) { Write-ToConsoleLog "Plan Mode enabled, no changes will be made. All actions will be logged as what would be performed." -IsWarning $TempLogFileForPlan = (New-TemporaryFile).FullName } $funcWriteToConsoleLog = ${function:Write-ToConsoleLog}.ToString() if($BypassConfirmation) { Write-ToConsoleLog "Bypass confirmation enabled, proceeding without prompts..." -IsWarning Write-ToConsoleLog "This is a highly destructive operation that will permanently delete GitHub resources!" -IsWarning Write-ToConsoleLog "We are waiting $BypassConfirmationTimeoutSeconds seconds to allow for cancellation. Press any key to cancel..." -IsWarning $keyPressed = $false $secondsRunning = 0 while((-not $keyPressed) -and ($secondsRunning -lt $BypassConfirmationTimeoutSeconds)){ $keyPressed = [Console]::KeyAvailable Write-ToConsoleLog ("Waiting for: $($BypassConfirmationTimeoutSeconds-$secondsRunning) seconds. Press any key to cancel...") -IsWarning -Overwrite Start-Sleep -Seconds 1 $secondsRunning++ } if($keyPressed) { Write-ToConsoleLog "Cancellation key pressed, exiting without making any changes..." -IsError return } } Write-ToConsoleLog "Thanks for providing the inputs, getting started..." -IsSuccess $hasRepositoryPatterns = $RepositoryNamePatterns.Count -gt 0 $hasTeamPatterns = $TeamNamePatterns.Count -gt 0 $hasRunnerGroupPatterns = $RunnerGroupNamePatterns.Count -gt 0 if(-not $hasRepositoryPatterns -and -not $hasTeamPatterns -and -not $hasRunnerGroupPatterns) { Write-ToConsoleLog "No patterns provided for repositories, teams, or runner groups. Nothing to do. Exiting..." -IsError return } # Discover resources to delete $repositoriesToDelete = @() $teamsToDelete = @() $runnerGroupsToDelete = @() # Discover repositories if($hasRepositoryPatterns) { Write-ToConsoleLog "Discovering repositories in organization: $Organization" $repositoriesResponse = (gh repo list $Organization --json name,url --limit 1000 2>&1) if($LASTEXITCODE -ne 0) { Write-ToConsoleLog "Failed to list repositories in organization: $Organization" -IsError return } $allRepositories = @($repositoriesResponse | ConvertFrom-Json) Write-ToConsoleLog "Found $($allRepositories.Count) total repositories in organization: $Organization" foreach($repo in $allRepositories) { foreach($pattern in $RepositoryNamePatterns) { if($repo.name -match $pattern) { Write-ToConsoleLog "Repository matches pattern '$pattern': $($repo.name)" $repositoriesToDelete += @{ Name = $repo.name Url = $repo.url } break } } } Write-ToConsoleLog "Found $($repositoriesToDelete.Count) repositories matching patterns for deletion" } # Discover teams if($hasTeamPatterns) { Write-ToConsoleLog "Discovering teams in organization: $Organization" $teamsResponse = (gh api "orgs/$Organization/teams" --paginate 2>&1) if($LASTEXITCODE -ne 0) { Write-ToConsoleLog "Failed to list teams in organization: $Organization" -IsError return } $allTeams = @($teamsResponse | ConvertFrom-Json) Write-ToConsoleLog "Found $($allTeams.Count) total teams in organization: $Organization" foreach($team in $allTeams) { foreach($pattern in $TeamNamePatterns) { if($team.name -match $pattern -or $team.slug -match $pattern) { Write-ToConsoleLog "Team matches pattern '$pattern': $($team.name) (slug: $($team.slug))" $teamsToDelete += @{ Name = $team.name Slug = $team.slug Id = $team.id } break } } } Write-ToConsoleLog "Found $($teamsToDelete.Count) teams matching patterns for deletion" } # Discover runner groups if($hasRunnerGroupPatterns) { Write-ToConsoleLog "Discovering runner groups in organization: $Organization" $runnerGroupsResponse = (gh api "orgs/$Organization/actions/runner-groups" --paginate 2>&1) if($LASTEXITCODE -ne 0) { Write-ToConsoleLog "Failed to list runner groups in organization: $Organization (may require GitHub Enterprise)" -IsWarning $allRunnerGroups = @() } else { $allRunnerGroups = ($runnerGroupsResponse | ConvertFrom-Json).runner_groups } if($null -ne $allRunnerGroups) { Write-ToConsoleLog "Found $($allRunnerGroups.Count) total runner groups in organization: $Organization" foreach($runnerGroup in $allRunnerGroups) { # Skip the default runner group as it cannot be deleted if($runnerGroup.name -eq "Default" -or $runnerGroup.default) { Write-ToConsoleLog "Skipping default runner group: $($runnerGroup.name)" continue } foreach($pattern in $RunnerGroupNamePatterns) { if($runnerGroup.name -match $pattern) { Write-ToConsoleLog "Runner group matches pattern '$pattern': $($runnerGroup.name)" $runnerGroupsToDelete += @{ Name = $runnerGroup.name Id = $runnerGroup.id } break } } } Write-ToConsoleLog "Found $($runnerGroupsToDelete.Count) runner groups matching patterns for deletion" } } # Confirm deletion $totalResourcesToDelete = $repositoriesToDelete.Count + $teamsToDelete.Count + $runnerGroupsToDelete.Count if($totalResourcesToDelete -eq 0) { Write-ToConsoleLog "No resources found matching the provided patterns. Nothing to delete." -IsWarning return } if(-not $BypassConfirmation) { Write-ToConsoleLog "The following GitHub resources will be deleted:" if($repositoriesToDelete.Count -gt 0) { Write-ToConsoleLog "Repositories ($($repositoriesToDelete.Count)):" $repositoriesToDelete | ForEach-Object { Write-ToConsoleLog " - $($_.Name)" } } if($teamsToDelete.Count -gt 0) { Write-ToConsoleLog "Teams ($($teamsToDelete.Count)):" $teamsToDelete | ForEach-Object { Write-ToConsoleLog " - $($_.Name) (slug: $($_.Slug))" } } if($runnerGroupsToDelete.Count -gt 0) { Write-ToConsoleLog "Runner Groups ($($runnerGroupsToDelete.Count)):" $runnerGroupsToDelete | ForEach-Object { Write-ToConsoleLog " - $($_.Name)" } } if($PlanMode) { Write-ToConsoleLog "Skipping confirmation for plan mode" } else { $continue = Invoke-PromptForConfirmation -message "ALL LISTED GITHUB RESOURCES WILL BE PERMANENTLY DELETED" if(-not $continue) { Write-ToConsoleLog "Exiting..." return } } } # Delete repositories if($repositoriesToDelete.Count -gt 0) { Write-ToConsoleLog "Deleting repositories..." $repositoriesToDelete | ForEach-Object -Parallel { $funcWriteToConsoleLog = $using:funcWriteToConsoleLog ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog $TempLogFileForPlan = $using:TempLogFileForPlan $org = $using:Organization $repo = $_ $repoFullName = "$org/$($repo.Name)" if($using:PlanMode) { Write-ToConsoleLog ` "Would delete repository: $repoFullName", ` "Would run: gh repo delete $repoFullName --yes" ` -IsPlan -LogFilePath $TempLogFileForPlan } else { Write-ToConsoleLog "Deleting repository: $repoFullName" $result = gh repo delete $repoFullName --yes 2>&1 if($LASTEXITCODE -ne 0) { Write-ToConsoleLog "Failed to delete repository: $repoFullName", "Full error: $result" -IsWarning } else { Write-ToConsoleLog "Deleted repository: $repoFullName" } } } -ThrottleLimit $ThrottleLimit } # Delete teams if($teamsToDelete.Count -gt 0) { Write-ToConsoleLog "Deleting teams..." $teamsToDelete | ForEach-Object -Parallel { $funcWriteToConsoleLog = $using:funcWriteToConsoleLog ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog $TempLogFileForPlan = $using:TempLogFileForPlan $org = $using:Organization $team = $_ if($using:PlanMode) { Write-ToConsoleLog ` "Would delete team: $($team.Name) (slug: $($team.Slug))", ` "Would run: gh api -X DELETE orgs/$org/teams/$($team.Slug)" ` -IsPlan -LogFilePath $TempLogFileForPlan } else { Write-ToConsoleLog "Deleting team: $($team.Name) (slug: $($team.Slug))" $result = gh api -X DELETE "orgs/$org/teams/$($team.Slug)" 2>&1 if($LASTEXITCODE -ne 0) { Write-ToConsoleLog "Failed to delete team: $($team.Name)", "Full error: $result" -IsWarning } else { Write-ToConsoleLog "Deleted team: $($team.Name)" } } } -ThrottleLimit $ThrottleLimit } # Delete runner groups if($runnerGroupsToDelete.Count -gt 0) { Write-ToConsoleLog "Deleting runner groups..." $runnerGroupsToDelete | ForEach-Object -Parallel { $funcWriteToConsoleLog = $using:funcWriteToConsoleLog ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog $TempLogFileForPlan = $using:TempLogFileForPlan $org = $using:Organization $runnerGroup = $_ if($using:PlanMode) { Write-ToConsoleLog ` "Would delete runner group: $($runnerGroup.Name)", ` "Would run: gh api -X DELETE orgs/$org/actions/runner-groups/$($runnerGroup.Id)" ` -IsPlan -LogFilePath $TempLogFileForPlan } else { Write-ToConsoleLog "Deleting runner group: $($runnerGroup.Name)" $result = gh api -X DELETE "orgs/$org/actions/runner-groups/$($runnerGroup.Id)" 2>&1 if($LASTEXITCODE -ne 0) { Write-ToConsoleLog "Failed to delete runner group: $($runnerGroup.Name)", "Full error: $result" -IsWarning } else { Write-ToConsoleLog "Deleted runner group: $($runnerGroup.Name)" } } } -ThrottleLimit $ThrottleLimit } Write-ToConsoleLog "Cleanup completed." -IsSuccess if($PlanMode) { Write-ToConsoleLog "Plan mode enabled, no changes were made." -IsWarning $planLogContents = Get-Content -Path $TempLogFileForPlan -Raw Write-ToConsoleLog @("Plan mode log contents:", $planLogContents) -Color Gray Remove-Item -Path $TempLogFileForPlan -Force } } } # SIG # Begin signature block # MIIoLwYJKoZIhvcNAQcCoIIoIDCCKBwCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCChEZ9s8gPWr85+ # 7aq41cCcTV++XYtf54ocEXaF16q/KqCCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z # 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy # 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi # 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ # hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ # 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe # UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk # tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj # Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS # DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns # WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO # lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71 # 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9 # nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk # C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm # M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn # lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo # STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGg8wghoLAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB # BQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIM0OE5l2VVai4mmR3ei3KIXn # 0Vx23atsytSTsUUIYDskMEQGCisGAQQBgjcCAQwxNjA0oBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQuY29tIDANBgkqhkiG9w0B # AQEFAASCAQCnxcsqGZGezAHPleah1pv3gHVe5H3G3EaFvtvnr6dv4N+1N/eAD5Yo # gSLij9Dip2AbZ0QUVGVFhPJCH8B09O3iFWMpsnadffoiCepFqSUR9cfTnnLPXHF4 # 2t+G8i2JE8TIYky5f1mFGrkP96ClZU/EascgnXqO8TmPJn8QGF/XpP5i+CRNHKS4 # 7m07iuvm38uCenDladCN7ww2wEBdR3isLnlM2swPAT4wNMfF1jD5sXp0Q1Fak6sh # ehExmJToFUhp2ogqV0I7MHg/y2sIy4leH3AH8CKzp6sM4ErjZOw/BU26dU0mwi7a # vXhGGpcH9bTz/aizPrYXAtQ38EgSU+PWoYIXlzCCF5MGCisGAQQBgjcDAwExgheD # MIIXfwYJKoZIhvcNAQcCoIIXcDCCF2wCAQMxDzANBglghkgBZQMEAgEFADCCAVIG # CyqGSIb3DQEJEAEEoIIBQQSCAT0wggE5AgEBBgorBgEEAYRZCgMBMDEwDQYJYIZI # AWUDBAIBBQAEIAHMdA9yZdmnc7BM5eI0ETCMFneREAlf4j4w+6ke7FlUAgZpaRT4 # XB0YEzIwMjYwMTMwMjAwNjE1LjkzM1owBIACAfSggdGkgc4wgcsxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBB # bWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjpBMDAw # LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZaCCEe0wggcgMIIFCKADAgECAhMzAAACCHidWF2Sx9lSAAEAAAIIMA0GCSqGSIb3 # DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk # BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDEzMDE5 # NDI1M1oXDTI2MDQyMjE5NDI1M1owgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlv # bnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjpBMDAwLTA1RTAtRDk0NzElMCMG # A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBALXLcAjmUjPcinWcrkExRRsZGyNKcLP9UazffO9j # Q16yGw+B+V3f9nf/d1LxYoEuUOWiyZck5mUVKI1dR3oNMnED2KT9lsJ1YnvwsqNs # 3e0WRfZzpFGlEykDyyr+gFtGvI/dzxD+DGkkAocfPxy5Kft7B8IvOc2bGqWJOTdD # kserPY+N5goP91sowFPZMABYC+6bjP8dcgnq0V0ag1XhZRFmzAJK3pE7BqpDWgBg # a8Sd0f4NdmrX5seyC9w80J1NIilahtCIlL9QouJHTYo0KoHgj3JqMVNKWcwgQzP8 # 2LnfygYjrimFy82lR7b6popYdnx3hPUqCG9GZJIXhgXkM0QlvFoJTCzLudQuawWd # No6NU6hMVZZ9Ze8G44qQFxApYYq+uSL3vqPjH7l7MA/fp+re7p0dElMtkC7h0S46 # ihTf6Qxmv5EFhaNMdAIpX7JnVJPR4aRsdegDaXLJEOU2MFByh5kjFYJm2z93f6d/ # WOJIs3p/rB0dpTPQAPA5ND9oSjUgLzl4V4+/IgprEUmQZTYyprpfOreoKrm2iQge # 2OGiCysSB8MpN4VdO12GXUg+0twJ8xxY4YYBeixVRTsb3jwXpb3rjbh/ZUcPwvcW # jIAj36vjPIhBSaqIRLO5BZ5alNOMVjAKaBdoY65INXxw05VAHog/M+d5mFVOPDFs # BmVpAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUozHi986pxROBg5UH1/Xz+aF6AU0w # HwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKg # UIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAw # XjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j # ZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQw # DAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E # BAMCB4AwDQYJKoZIhvcNAQELBQADggIBAFGzMokrAB+zCp1pA8WJpgH9k0BhYNrT # jRdNxCwJoK5rmewbUiyVhpKkfuaJMuvp5ASpdzNmip45r/G8OcwaJ8Y11rQIdtDC # 2mciGy62so7aOGMRobCUmA4yqXbWvsiTpecHNrR7eEE67hQGQyX8sRf4BRG3uLv5 # FM2wY3Rxc/A9JtMUT73PtZqAZtj2nBSj83GQYmx6oYJD/0rZUxTvhvDl7v7wgZSE # zbGykk+qdJ4c+FiZwHRZyU7FxUh+P9m5C/Cis9tMQRgNULNI3ftzTIKE8xjsUn4c # YUE0nHB3mUoivsZh+rxrSA6ILaWMZiVziu3hwJ53VcqDzd/SX1pRWKZYFhe1815u # Gl+votzeMPw2CysOHO3RaCch2dNkKLuPGOwgGKUf32ljn+HptBwsor8TooI/0TVg # 3vx8to5eRczI7rEuu9Bn64JKLWF1O58ULuhIH8JTlFt8hUdcbSPWjafW2d7h4Js1 # 8qpQ9MTfW01tYFHbdiLLSveRCYd5gTUYtsvinCSepqKnUFGfpYhQwm2CdxrAQ3fd # /wBgZnhrc2ceinMZVXqd598ZVqDhN27L6jLVgX6yEKGhd0yp+E9YWkd7e4kZPgYk # SI2zj7bxr/AdS4X5pFpHRw3k/teU7BTXfrSJQIm1B28pBo0DAYjb0o7BLdAauJH0 # XaM4Y9QCWl4TMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg # VGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+ # F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU # 88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqY # O7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzp # cGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0Xn # Rm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1 # zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZN # N3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLR # vWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTY # uVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUX # k8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB # 2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKR # PEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0g # BFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5t # aWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQM # MAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQE # AwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQ # W9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNv # bS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBa # BggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqG # SIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOX # PTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6c # qYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/z # jj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz # /AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyR # gNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdU # bZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo # 3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4K # u+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10Cga # iQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9 # vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGC # A1AwggI4AgEBMIH5oYHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMV # AI2S+7Q0pxKN1grKuEllyzJc5RM0oIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh # bXAgUENBIDIwMTAwDQYJKoZIhvcNAQELBQACBQDtJ1gPMCIYDzIwMjYwMTMwMTYx # NzE5WhgPMjAyNjAxMzExNjE3MTlaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAO0n # WA8CAQAwCgIBAAICE18CAf8wBwIBAAICEz4wCgIFAO0oqY8CAQAwNgYKKwYBBAGE # WQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDAN # BgkqhkiG9w0BAQsFAAOCAQEAuprll6Fu9W1p31D624laoGkJAD3RCygnSJXTv6TZ # DIg9TNq1s2/P7gUBJFGVsiwKghOCHdavRAByAjbve1YGkOaV76idY89VtWYWlEmS # t6bYoI0Ww+Oh1/YRe2M2LNR2nhevxhyyP2XvFrNO4yWVCgara6l3ItF2o2LpYQNf # 8CBWBQf68WyGJi/vOAxycf8RDXtMFxaxtUD+RDPgHaVq3BfVIknm71kGF/CZF9CK # w55P0c9QySq0ifJPwJ3h2RCeXD3GuzyYGErIYNx4uNhNiG2V3VbKbtyPwkTzgW0K # nYE26Qy+XkOhk/jJewnZB7MonsUj259cpooZzdzwOkdoyjGCBA0wggQJAgEBMIGT # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACCHidWF2Sx9lSAAEA # AAIIMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQ # AQQwLwYJKoZIhvcNAQkEMSIEIFgHC4SBh/ChW0DbaKN9OmARDH3fyEkX+WpgnQGU # JwmnMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgj/+ObwkdrZbU73vvy334 # W3Znk2Yqq20+TpD71FGmZ6kwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ # Q0EgMjAxMAITMwAAAgh4nVhdksfZUgABAAACCDAiBCBatbdbk/9Pzw/y63IMo6Gx # 2p0kR8/hWMZVNgSCbyWD9zANBgkqhkiG9w0BAQsFAASCAgAk5RoAgR36sjPnv1di # xQOMJKwYnc28zFye/ORMZoSGmJLXJO286A9g4TJf+2wl7hG1zBWXL0GikWUMp+Ry # jd0VAsWo+89GktPOBMPODgI0iZXBYDLarnjmLTZvTqBAjfSnwkftyVyKVHCIzqVr # ekjRx5h6oG1/XDVTt/3ydytiZgK+rmnoKES6GYrSBI0JM4/a/FAblSJLznDn+CLe # hApZm0XuXxaK7sHyaic5/eJM68Xi2tgcu+hU9flzv3/qajbBBdohmzCRM1iGI58U # CJ8S9lTGZi4f8fw4ZXLekBb2utFfIwfh9M19/ZPAfHYYkpKp62e0PVNO9dKDCd/A # RD70wfPsULc/bBxQ6W7fMSm2VPgkdN7kZHj1kwc01SNIrwNAdK3eXWAPOSbvq4/P # Uczr07bohKmOZY85lpIck1MFPH4eeX33lMEn5oheuA5L6oA+F5rdUkY9YKAezBo1 # hMqZhzAyn3JKkQ/CG8BIKAtU6sbWQSVUtTB5W8XDNN6ZMvGPr/woIbhTEhYToLaa # Ziw9zBrOc2GrZ/EVbZ/+vej+HKZwt4Za0IdhyVy6oRbbnOarl7n4YHKsSbCZII0C # X8P0sCFKH21h212ThExDDfztHGbfzfeuqBlLD7vRRH8P4vpGARtLVZx9C+4tC4S9 # k6SPPNhdCRe7+9WZTTxaxRXMwg== # SIG # End signature block |