...
1#!/usr/bin/env pwsh
2
3# Copyright (c) Microsoft Corporation. All rights reserved.
4# Licensed under the MIT License.
5
6#Requires -Version 6.0
7#Requires -PSEdition Core
8#Requires -Modules @{ModuleName='Az.Accounts'; ModuleVersion='1.6.4'}
9#Requires -Modules @{ModuleName='Az.Resources'; ModuleVersion='1.8.0'}
10
11[CmdletBinding(DefaultParameterSetName = 'Default', SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
12param (
13 # Limit $BaseName to enough characters to be under limit plus prefixes, and https://docs.microsoft.com/azure/architecture/best-practices/resource-naming.
14 [Parameter(ParameterSetName = 'Default')]
15 [Parameter(ParameterSetName = 'Default+Provisioner', Mandatory = $true, Position = 0)]
16 [ValidatePattern('^[-a-zA-Z0-9\.\(\)_]{0,80}(?<=[a-zA-Z0-9\(\)])$')]
17 [string] $BaseName,
18
19 [Parameter(ParameterSetName = 'ResourceGroup')]
20 [Parameter(ParameterSetName = 'ResourceGroup+Provisioner')]
21 [string] $ResourceGroupName,
22
23 [Parameter(ParameterSetName = 'Default+Provisioner', Mandatory = $true)]
24 [Parameter(ParameterSetName = 'ResourceGroup+Provisioner', Mandatory = $true)]
25 [ValidateNotNullOrEmpty()]
26 [string] $TenantId,
27
28 [Parameter()]
29 [ValidatePattern('^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$')]
30 [string] $SubscriptionId,
31
32 [Parameter(ParameterSetName = 'Default+Provisioner', Mandatory = $true)]
33 [Parameter(ParameterSetName = 'ResourceGroup+Provisioner', Mandatory = $true)]
34 [ValidatePattern('^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$')]
35 [string] $ProvisionerApplicationId,
36
37 [Parameter(ParameterSetName = 'Default+Provisioner', Mandatory = $true)]
38 [Parameter(ParameterSetName = 'ResourceGroup+Provisioner', Mandatory = $true)]
39 [string] $ProvisionerApplicationSecret,
40
41 [Parameter(ParameterSetName = 'Default', Position = 0)]
42 [Parameter(ParameterSetName = 'Default+Provisioner')]
43 [Parameter(ParameterSetName = 'ResourceGroup')]
44 [Parameter(ParameterSetName = 'ResourceGroup+Provisioner')]
45 [string] $ServiceDirectory,
46
47 [Parameter()]
48 [ValidateSet('AzureCloud', 'AzureUSGovernment', 'AzureChinaCloud', 'Dogfood')]
49 [string] $Environment = 'AzureCloud',
50
51 [Parameter(ParameterSetName = 'ResourceGroup')]
52 [Parameter(ParameterSetName = 'ResourceGroup+Provisioner')]
53 [switch] $CI,
54
55 [Parameter()]
56 [switch] $Force,
57
58 # Captures any arguments not declared here (no parameter errors)
59 [Parameter(ValueFromRemainingArguments = $true)]
60 $RemoveTestResourcesRemainingArguments
61)
62
63# By default stop for any error.
64if (!$PSBoundParameters.ContainsKey('ErrorAction')) {
65 $ErrorActionPreference = 'Stop'
66}
67
68# Support actions to invoke on exit.
69$exitActions = @({
70 if ($exitActions.Count -gt 1) {
71 Write-Verbose 'Running registered exit actions.'
72 }
73})
74
75trap {
76 # Like using try..finally in PowerShell, but without keeping track of more braces or tabbing content.
77 $exitActions.Invoke()
78}
79
80. $PSScriptRoot/SubConfig-Helpers.ps1
81# Source helpers to purge resources.
82. "$PSScriptRoot\..\scripts\Helpers\Resource-Helpers.ps1"
83
84function Log($Message) {
85 Write-Host ('{0} - {1}' -f [DateTime]::Now.ToLongTimeString(), $Message)
86}
87
88function Retry([scriptblock] $Action, [int] $Attempts = 5) {
89 $attempt = 0
90 $sleep = 5
91
92 while ($attempt -lt $Attempts) {
93 try {
94 $attempt++
95 return $Action.Invoke()
96 } catch {
97 if ($attempt -lt $Attempts) {
98 $sleep *= 2
99
100 Write-Warning "Attempt $attempt failed: $_. Trying again in $sleep seconds..."
101 Start-Sleep -Seconds $sleep
102 } else {
103 Write-Error -ErrorRecord $_
104 }
105 }
106 }
107}
108
109if ($ProvisionerApplicationId) {
110 $null = Disable-AzContextAutosave -Scope Process
111
112 Log "Logging into service principal '$ProvisionerApplicationId'"
113 $provisionerSecret = ConvertTo-SecureString -String $ProvisionerApplicationSecret -AsPlainText -Force
114 $provisionerCredential = [System.Management.Automation.PSCredential]::new($ProvisionerApplicationId, $provisionerSecret)
115
116 # Use the given subscription ID if provided.
117 $subscriptionArgs = if ($SubscriptionId) {
118 @{SubscriptionId = $SubscriptionId}
119 }
120
121 $provisionerAccount = Retry {
122 Connect-AzAccount -Force:$Force -Tenant $TenantId -Credential $provisionerCredential -ServicePrincipal -Environment $Environment @subscriptionArgs
123 }
124
125 $exitActions += {
126 Write-Verbose "Logging out of service principal '$($provisionerAccount.Context.Account)'"
127 $null = Disconnect-AzAccount -AzureContext $provisionerAccount.Context
128 }
129}
130
131$context = Get-AzContext
132
133if (!$ResourceGroupName) {
134 if ($CI) {
135 if (!$ServiceDirectory) {
136 Write-Warning "ServiceDirectory parameter is empty, nothing to remove"
137 exit 0
138 }
139 $envVarName = (BuildServiceDirectoryPrefix (GetServiceLeafDirectoryName $ServiceDirectory)) + "RESOURCE_GROUP"
140 $ResourceGroupName = [Environment]::GetEnvironmentVariable($envVarName)
141 if (!$ResourceGroupName) {
142 Write-Error "Could not find resource group name environment variable '$envVarName'. This is likely due to an earlier failure in the 'Deploy Test Resources' step above."
143 exit 0
144 }
145 } else {
146 if (!$BaseName) {
147 $UserName = GetUserName
148 $BaseName = GetBaseName $UserName $ServiceDirectory
149 Log "BaseName was not set. Using default base name '$BaseName'"
150 }
151
152 # Format the resource group name like in New-TestResources.ps1.
153 $ResourceGroupName = "rg-$BaseName"
154 }
155}
156
157# If no subscription was specified, try to select the Azure SDK Developer Playground subscription.
158# Ignore errors to leave the automatically selected subscription.
159if ($SubscriptionId) {
160 $currentSubcriptionId = $context.Subscription.Id
161 if ($currentSubcriptionId -ne $SubscriptionId) {
162 Log "Selecting subscription '$SubscriptionId'"
163 $null = Select-AzSubscription -Subscription $SubscriptionId
164
165 $exitActions += {
166 Log "Selecting previous subscription '$currentSubcriptionId'"
167 $null = Select-AzSubscription -Subscription $currentSubcriptionId
168 }
169
170 # Update the context.
171 $context = Get-AzContext
172 }
173} else {
174 Log "Attempting to select subscription 'Azure SDK Developer Playground (faa080af-c1d8-40ad-9cce-e1a450ca5b57)'"
175 $null = Select-AzSubscription -Subscription 'faa080af-c1d8-40ad-9cce-e1a450ca5b57' -ErrorAction Ignore
176
177 # Update the context.
178 $context = Get-AzContext
179
180 $SubscriptionId = $context.Subscription.Id
181 $PSBoundParameters['SubscriptionId'] = $SubscriptionId
182}
183
184# Use cache of well-known team subs without having to be authenticated.
185$wellKnownSubscriptions = @{
186 'faa080af-c1d8-40ad-9cce-e1a450ca5b57' = 'Azure SDK Developer Playground'
187 'a18897a6-7e44-457d-9260-f2854c0aca42' = 'Azure SDK Engineering System'
188 '2cd617ea-1866-46b1-90e3-fffb087ebf9b' = 'Azure SDK Test Resources'
189}
190
191# Print which subscription is currently selected.
192$subscriptionName = $context.Subscription.Id
193if ($wellKnownSubscriptions.ContainsKey($subscriptionName)) {
194 $subscriptionName = '{0} ({1})' -f $wellKnownSubscriptions[$subscriptionName], $subscriptionName
195}
196
197Log "Selected subscription '$subscriptionName'"
198
199if ($ServiceDirectory) {
200 $root = [System.IO.Path]::Combine("$PSScriptRoot/../../../sdk", $ServiceDirectory) | Resolve-Path
201 $preRemovalScript = Join-Path -Path $root -ChildPath 'remove-test-resources-pre.ps1'
202 if (Test-Path $preRemovalScript) {
203 Log "Invoking pre resource removal script '$preRemovalScript'"
204
205 if (!$PSCmdlet.ParameterSetName.StartsWith('ResourceGroup')) {
206 $PSBoundParameters.Add('ResourceGroupName', $ResourceGroupName);
207 }
208
209 &$preRemovalScript @PSBoundParameters
210 }
211
212 # Make sure environment files from New-TestResources -OutFile are removed.
213 Get-ChildItem -Path $root -Filter test-resources.json.env -Recurse | Remove-Item -Force:$Force
214}
215
216$verifyDeleteScript = {
217 try {
218 $group = Get-AzResourceGroup -name $ResourceGroupName
219 } catch {
220 if ($_.ToString().Contains("Provided resource group does not exist")) {
221 Write-Verbose "Resource group '$ResourceGroupName' not found. Continuing..."
222 return
223 }
224 throw $_
225 }
226
227 if ($group.ProvisioningState -ne "Deleting")
228 {
229 throw "Resource group is in '$($group.ProvisioningState)' state, expected 'Deleting'"
230 }
231}
232
233# Get any resources that can be purged after the resource group is deleted coerced into a collection even if empty.
234$purgeableResources = Get-PurgeableGroupResources $ResourceGroupName
235
236Log "Deleting resource group '$ResourceGroupName'"
237if ($Force -and !$purgeableResources) {
238 Remove-AzResourceGroup -Name "$ResourceGroupName" -Force:$Force -AsJob
239 Write-Verbose "Running background job to delete resource group '$ResourceGroupName'"
240
241 Retry $verifyDeleteScript 3
242} else {
243 # Don't swallow interactive confirmation when Force is false
244 Remove-AzResourceGroup -Name "$ResourceGroupName" -Force:$Force
245}
246
247# Now purge the resources that should have been deleted with the resource group.
248Remove-PurgeableResources $purgeableResources
249
250$exitActions.Invoke()
251
252<#
253.SYNOPSIS
254Deletes the resource group deployed for a service directory from Azure.
255
256.DESCRIPTION
257Removes a resource group and all its resources previously deployed using
258New-TestResources.ps1.
259If you are not currently logged into an account in the Az PowerShell module,
260you will be asked to log in with Connect-AzAccount. Alternatively, you (or a
261build pipeline) can pass $ProvisionerApplicationId and
262$ProvisionerApplicationSecret to authenticate a service principal with access to
263create resources.
264
265.PARAMETER BaseName
266A name to use in the resource group and passed to the ARM template as 'baseName'.
267This will delete the resource group named 'rg-<baseName>'
268
269.PARAMETER ResourceGroupName
270The name of the resource group to delete.
271
272.PARAMETER TenantId
273The tenant ID of a service principal when a provisioner is specified.
274
275.PARAMETER SubscriptionId
276Optional subscription ID to use when deleting resources when logging in as a
277provisioner. You can also use Set-AzContext if not provisioning.
278
279If you do not specify a SubscriptionId and are not logged in, one will be
280automatically selected for you by the Connect-AzAccount cmdlet.
281
282Once you are logged in (or were previously), the selected SubscriptionId
283will be used for subsequent operations that are specific to a subscription.
284
285.PARAMETER ProvisionerApplicationId
286A service principal ID to provision test resources when a provisioner is specified.
287
288.PARAMETER ProvisionerApplicationSecret
289A service principal secret (password) to provision test resources when a provisioner is specified.
290
291.PARAMETER ServiceDirectory
292A directory under 'sdk' in the repository root - optionally with subdirectories
293specified - in which to discover pre removal script named 'remove-test-resources-pre.json'.
294
295.PARAMETER Environment
296Name of the cloud environment. The default is the Azure Public Cloud
297('PublicCloud')
298
299.PARAMETER CI
300Run script in CI mode. Infers various environment variable names based on CI convention.
301
302.PARAMETER Force
303Force removal of resource group without asking for user confirmation
304
305.EXAMPLE
306Remove-TestResources.ps1 keyvault -Force
307Use the currently logged-in account to delete the resources created for Key Vault testing.
308
309.EXAMPLE
310Remove-TestResources.ps1 `
311 -ResourceGroupName "${env:AZURE_RESOURCEGROUP_NAME}" `
312 -TenantId '$(TenantId)' `
313 -ProvisionerApplicationId '$(AppId)' `
314 -ProvisionerApplicationSecret '$(AppSecret)' `
315 -Force `
316 -Verbose `
317When run in the context of an Azure DevOps pipeline, this script removes the
318resource group whose name is stored in the environment variable
319AZURE_RESOURCEGROUP_NAME.
320
321#>
View as plain text