diff --git a/eng/New-TestResources.cmd b/eng/New-TestResources.cmd new file mode 100644 index 000000000000..94b0c1f2db1a --- /dev/null +++ b/eng/New-TestResources.cmd @@ -0,0 +1,17 @@ +@echo off + +REM Copyright (c) Microsoft Corporation. All rights reserved. +REM Licensed under the MIT License. + +setlocal + +for /f "usebackq delims=" %%i in (`where pwsh 2^>nul`) do ( + set _cmd=%%i +) + +if "%_cmd%"=="" ( + echo Error: PowerShell not found. Please visit https://github.com/powershell/powershell for install instructions. + exit /b 2 +) + +call "%_cmd%" -NoLogo -NoProfile -File "%~dpn0.ps1" %* diff --git a/eng/New-TestResources.ps1 b/eng/New-TestResources.ps1 new file mode 100644 index 000000000000..f40058d9e2c7 --- /dev/null +++ b/eng/New-TestResources.ps1 @@ -0,0 +1,307 @@ +#!/usr/bin/env pwsh + +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +#Requires -Version 6.0 +#Requires -PSEdition Core +#Requires -Modules @{ModuleName='Az.Accounts'; ModuleVersion='1.6.4'} +#Requires -Modules @{ModuleName='Az.Resources'; ModuleVersion='1.8.0'} + +[CmdletBinding(DefaultParameterSetName = 'Default', SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] +param ( + # Limit $BaseName to enough characters to be under limit plus prefixes, and https://docs.microsoft.com/azure/architecture/best-practices/resource-naming. + [Parameter(Mandatory = $true, Position = 0)] + [ValidatePattern('^[-a-zA-Z0-9\.\(\)_]{0,80}(?<=[a-zA-Z0-9\(\)])$')] + [string] $BaseName, + + [Parameter(Mandatory = $true)] + [string] $ServiceDirectory, + + [Parameter(Mandatory = $true)] + [ValidatePattern('^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$')] + [string] $TestApplicationId, + + [Parameter()] + [string] $TestApplicationSecret, + + [Parameter()] + [ValidatePattern('^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$')] + [string] $TestApplicationOid, + + [Parameter(ParameterSetName = 'Provisioner', Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] $TenantId, + + [Parameter(ParameterSetName = 'Provisioner', Mandatory = $true)] + [ValidatePattern('^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$')] + [string] $ProvisionerApplicationId, + + [Parameter(ParameterSetName = 'Provisioner', Mandatory = $true)] + [string] $ProvisionerApplicationSecret, + + [Parameter(ParameterSetName = 'Provisioner')] + [switch] $NoProvisionerAutoSave, + + [Parameter()] + [ValidateRange(0, [int]::MaxValue)] + [int] $DeleteAfterHours, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] $Location = 'westus2', + + [Parameter()] + [ValidateNotNullOrEmpty()] + [hashtable] $AdditionalParameters, + + [Parameter()] + [switch] $Force +) + +# By default stop for any error. +if (!$PSBoundParameters.ContainsKey('ErrorAction')) { + $ErrorActionPreference = 'Stop' +} + +function Log($Message) { + Write-Host ('{0} - {1}' -f [DateTime]::Now.ToLongTimeString(), $Message) +} + +# Support actions to invoke on exit. +$exitActions = @({ + if ($exitActions.Count -gt 1) { + Write-Verbose 'Running registered exit actions' + } +}) + +trap { + # Like using try..finally in PowerShell, but without keeping track of more braces or tabbing content. + $exitActions.Invoke() +} + +# Enumerate test resources to deploy. Fail if none found. +$root = Resolve-Path -Path "$PSScriptRoot/../sdk/$ServiceDirectory" +$templateFileName = 'test-resources.json' +$templateFiles = @() + +Write-Verbose "Checking for '$templateFileName' files under '$root'" +Get-ChildItem -Path $root -Filter $templateFileName -Recurse | ForEach-Object { + $templateFile = $_.FullName + + Write-Verbose "Found template '$templateFile'" + $templateFiles += $templateFile +} + +if (!$templateFiles) { + Write-Warning -Message "No template files found under '$root'" + exit +} + +# Log in if requested; otherwise, the user is expected to already be authenticated via Connect-AzAccount. +if ($ProvisionerApplicationId) { + $null = Disable-AzContextAutosave -Scope Process + + Log "Logging into service principal '$ProvisionerApplicationId'" + $provisionerSecret = ConvertTo-SecureString -String $ProvisionerApplicationSecret -AsPlainText -Force + $provisionerCredential = [System.Management.Automation.PSCredential]::new($ProvisionerApplicationId, $provisionerSecret) + $provisionerAccount = Connect-AzAccount -Tenant $TenantId -Credential $provisionerCredential -ServicePrincipal + + $exitActions += { + Write-Verbose "Logging out of service principal '$($provisionerAccount.Context.Account)'" + $null = Disconnect-AzAccount -AzureContext $provisionerAccount.Context + } +} + +# Get test application OID from ID if not already provided. +if ($TestApplicationId -and !$TestApplicationOid) { + $testServicePrincipal = Get-AzADServicePrincipal -ApplicationId $TestApplicationId + if ($testServicePrincipal -and $testServicePrincipal.Id) { + $script:TestApplicationOid = $testServicePrincipal.Id + } +} + +# Format the resource group name based on resource group naming recommendations and limitations. +$resourceGroupName = "rg-{0}-$baseName" -f ($ServiceDirectory -replace '[\\\/]', '-').Substring(0, [Math]::Min($ServiceDirectory.Length, 90 - $BaseName.Length - 4)).Trim('-') + +# Tag the resource group to be deleted after a certain number of hours if specified. +$tags = @{ + Creator = if ($env:USER) { $env:USER } else { "${env:USERNAME}" } + ServiceDirectory = $ServiceDirectory +} + +if ($PSBoundParameters.ContainsKey('DeleteAfterHours')) { + $deleteAfter = [DateTime]::UtcNow.AddHours($DeleteAfterHours) + $tags.Add('DeleteAfter', $deleteAfter.ToString('o')) +} + +if ($env:SYSTEM_TEAMPROJECTID) { + # Add tags for the current CI job. + $tags += @{ + BuildId = "${env:BUILD_BUILDID}" + BuildJob = "${env:AGENT_JOBNAME}" + BuildNumber = "${env:BUILD_BUILDNUMBER}" + BuildReason = "${env:BUILD_REASON}" + } + + # Set the resource group name variable. + Write-Host "##vso[task.setvariable variable=AZURE_RESOURCEGROUP_NAME;]$resourceGroupName" +} + +Log "Creating resource group '${resourceGroupName}' in location '$Location'" +$resourceGroup = New-AzResourceGroup -Name "${resourceGroupName}" -Location $Location -Tag $tags -Force:$Force +if ($resourceGroup.ProvisioningState -eq 'Succeeded') { + # New-AzResourceGroup would've written an error and stopped the pipeline by default anyway. + Write-Verbose "Successfully created resource group '$($resourceGroup.ResourceGroupName)'" +} + +# Populate the template parameters and merge any additional specified. +$templateParameters = @{ + baseName = $BaseName + testApplicationId = $TestApplicationId + testApplicationOid = "$TestApplicationOid" +} + +if ($TenantId) { + $templateParameters.Add('tenantId', $TenantId) +} +if ($TestApplicationSecret) { + $templateParameters.Add('testApplicationSecret', $TestApplicationSecret) +} +if ($AdditionalParameters) { + $templateParameters += $AdditionalParameters +} + +# Try to detect the shell based on the parent process name (e.g. launch via shebang). +$shell, $shellExportFormat = if (($parentProcessName = (Get-Process -Id $PID).Parent.ProcessName) -and $parentProcessName -eq 'cmd') { + 'cmd', 'set {0}={1}' +} elseif (@('bash', 'csh', 'tcsh', 'zsh') -contains $parentProcessName) { + 'shell', 'export {0}={1}' +} else { + 'PowerShell', '$env:{0} = ''{1}''' +} + +foreach ($templateFile in $templateFiles) { + # Deployment fails if we pass in more parameters than are defined. + Write-Verbose "Removing unnecessary parameters from template '$templateFile'" + $templateJson = Get-Content -LiteralPath $templateFile | ConvertFrom-Json + $templateParameterNames = $templateJson.parameters.PSObject.Properties.Name + + $templateFileParameters = $templateParameters.Clone() + foreach ($key in $templateParameters.Keys) { + if ($templateParameterNames -notcontains $key) { + Write-Verbose "Removing unnecessary parameter '$key'" + $templateFileParameters.Remove($key) + } + } + + $preDeploymentScript = $templateFile | Split-Path | Join-Path -ChildPath 'test-resources-pre.ps1' + if (Test-Path $preDeploymentScript) { + Log "Invoking pre-deployment script '$preDeploymentScript'" + &$preDeploymentScript -ResourceGroupName $resourceGroupName @PSBoundParameters + } + + Log "Deploying template '$templateFile' to resource group '$($resourceGroup.ResourceGroupName)'" + $deployment = New-AzResourceGroupDeployment -Name $BaseName -ResourceGroupName $resourceGroup.ResourceGroupName -TemplateFile $templateFile -TemplateParameterObject $templateFileParameters + if ($deployment.ProvisioningState -eq 'Succeeded') { + # New-AzResourceGroupDeployment would've written an error and stopped the pipeline by default anyway. + Write-Verbose "Successfully deployed template '$templateFile' to resource group '$($resourceGroup.ResourceGroupName)'" + } + + if ($deployment.Outputs.Count -and !$env:SYSTEM_TEAMPROJECTID) { + # Write an extra new line to isolate the environment variables for easy reading. + Log "Persist the following environment variables based on your detected shell ($shell):`n" + } + + $deploymentOutputs = @{} + foreach ($key in $deployment.Outputs.Keys) { + $variable = $deployment.Outputs[$key] + + # Work around bug that makes the first few characters of environment variables be lowercase. + $key = $key.ToUpperInvariant() + + if ($variable.Type -eq 'String' -or $variable.Type -eq 'SecureString') { + $deploymentOutputs[$key] = $variable.Value + + if ($env:SYSTEM_TEAMPROJECTID) { + # Running in Azure Pipelines. Unfortunately, there's no good way to know which outputs are truly secrets + # because we have to set all output variables to "String" instead of "SecureString" or we will never get back a value. + Write-Host "##vso[task.setvariable variable=$key;issecret=true;]$($variable.Value)" + } else { + Write-Host ($shellExportFormat -f $key, $variable.Value) + } + } + } + + if ($key) { + # Isolate the environment variables for easy reading. + Write-Host "`n" + $key = $null + } + + $postDeploymentScript = $templateFile | Split-Path | Join-Path -ChildPath 'test-resources-post.ps1' + if (Test-Path $postDeploymentScript) { + Log "Invoking post-deployment script '$postDeploymentScript'" + &$postDeploymentScript -ResourceGroupName $resourceGroupName -DeploymentOutputs $deploymentOutputs @PSBoundParameters + } +} + +$exitActions.Invoke() + +<# +.SYNOPSIS +Deploys resources defined for a service directory to Azure. + +.DESCRIPTION +If a service directory contains one or more ARM templates named test-resources.json, they will be deployed to Azure. + +A service principal must first be created before this script is run and passed to $TestApplicationId and $TestApplicationSecret. Test resources will grant this service principal access. + +If you are not currently logged into an account in the Az PowerShell module, you will be asked to log in with Connect-AzAccount. Alternatively, you (or a build pipeline) can pass $ProvisionerApplicationId and $ProvisionerApplicationSecret to authenticate a service principal with access to create resources. + +.PARAMETER BaseName +A name to use in the resource group and passed to the ARM template as 'baseName'. + +.PARAMETER ServiceDirectory +A directory under 'sdk' in the repository root - optionally with subdirectories specified - in which to discover ARM templates named 'test-resources.json'. + +.PARAMETER TestApplicationId +A service principal ID to authenticate the test runner against deployed resources. + +.PARAMETER TestApplicationSecret +Optional service principal secret (password) to authenticate the test runner against deployed resources. + +.PARAMETER TenantId +The tenant ID of a service principal when a provisioner is specified. + +.PARAMETER ProvisionerApplicationId +A service principal ID to provision test resources when a provisioner is specified. + +.PARAMETER ProvisionerApplicationSecret +A service principal secret (password) to provision test resources when a provisioner is specified. + +.PARAMETER NoProvisionerAutoSave +Do not save credentials for the provisioner in the current process. + +.PARAMETER DeleteAfterHours +Optional number of hours after which the resource group is deleted. By default, the resource group will persist until you delete it. + +.PARAMETER Location +Optional location where resources should be created. By default this is 'westus2'. + +.PARAMETER AdditionalParameters +Optional key-value pairs of parameters to pass to the ARM template(s). + +.PARAMETER Force +Force creation of resources instead of being prompted. + +.EXAMPLE +./New-Template.ps1 -BaseName uuid123 -ServiceDirectory keyvault -TestApplicationId $env:AZURE_CLIENT_ID -TestApplicationSecret $env:AZURE_CLIENT_SECRET + +Use the currently logged-in account to provision new resources in the sdk/keyvault/test-resources.json ARM template and allow the service principal ID in environment variable AZURE_CLIENT_ID to access it. + +To create a service principal in your current subscription, run: New-AzADServicePrincipal. Save the returned Id as $env:AZURE_CLIENT_ID and Secret (piped to ConvertFrom-SecureString) as $env:AZURE_CLIENT_SECRET. + +.LINK +Remove-TestResources.ps1 +#> diff --git a/eng/Remove-TestResources.cmd b/eng/Remove-TestResources.cmd new file mode 100644 index 000000000000..94b0c1f2db1a --- /dev/null +++ b/eng/Remove-TestResources.cmd @@ -0,0 +1,17 @@ +@echo off + +REM Copyright (c) Microsoft Corporation. All rights reserved. +REM Licensed under the MIT License. + +setlocal + +for /f "usebackq delims=" %%i in (`where pwsh 2^>nul`) do ( + set _cmd=%%i +) + +if "%_cmd%"=="" ( + echo Error: PowerShell not found. Please visit https://github.com/powershell/powershell for install instructions. + exit /b 2 +) + +call "%_cmd%" -NoLogo -NoProfile -File "%~dpn0.ps1" %* diff --git a/eng/Remove-TestResources.ps1 b/eng/Remove-TestResources.ps1 new file mode 100644 index 000000000000..389d8d39da0c --- /dev/null +++ b/eng/Remove-TestResources.ps1 @@ -0,0 +1,119 @@ +#!/usr/bin/env pwsh + +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +#Requires -Version 6.0 +#Requires -PSEdition Core +#Requires -Modules @{ModuleName='Az.Accounts'; ModuleVersion='1.6.4'} +#Requires -Modules @{ModuleName='Az.Resources'; ModuleVersion='1.8.0'} + +[CmdletBinding(DefaultParameterSetName = 'Default', SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] +param ( + # Limit $BaseName to enough characters to be under limit plus prefixes, and https://docs.microsoft.com/azure/architecture/best-practices/resource-naming. + [Parameter(Mandatory = $true, Position = 0)] + [ValidatePattern('^[-a-zA-Z0-9\.\(\)_]{0,80}(?<=[a-zA-Z0-9\(\)])$')] + [string] $BaseName, + + # TODO: When https://github.com/Azure/azure-sdk-for-net/issues/9061 is resolved, default this to previously saved data. + [Parameter(Mandatory = $true)] + [string] $ServiceDirectory, + + [Parameter(ParameterSetName = 'Provisioner', Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] $TenantId, + + [Parameter(ParameterSetName = 'Provisioner', Mandatory = $true)] + [ValidatePattern('^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$')] + [string] $ProvisionerApplicationId, + + [Parameter(ParameterSetName = 'Provisioner', Mandatory = $true)] + [string] $ProvisionerApplicationSecret, + + [Parameter()] + [switch] $Force +) + +# By default stop for any error. +if (!$PSBoundParameters.ContainsKey('ErrorAction')) { + $ErrorActionPreference = 'Stop' +} + +function Log($Message) { + Write-Host ('{0} - {1}' -f [DateTime]::Now.ToLongTimeString(), $Message) +} + +# Support actions to invoke on exit. +$exitActions = @({ + if ($exitActions.Count -gt 1) { + Write-Verbose 'Running registered exit actions.' + } +}) + +trap { + # Like using try..finally in PowerShell, but without keeping track of more braces or tabbing content. + $exitActions.Invoke() +} + +if ($ProvisionerApplicationId) { + $null = Disable-AzContextAutosave -Scope Process + + Log "Logging into service principal '$ProvisionerApplicationId'" + $provisionerSecret = ConvertTo-SecureString -String $ProvisionerApplicationSecret -AsPlainText -Force + $provisionerCredential = [System.Management.Automation.PSCredential]::new($ProvisionerApplicationId, $provisionerSecret) + $provisionerAccount = Connect-AzAccount -Tenant $TenantId -Credential $provisionerCredential -ServicePrincipal + + $exitActions += { + Write-Verbose "Logging out of service principal '$($provisionerAccount.Context.Account)'" + $null = Disconnect-AzAccount -AzureContext $provisionerAccount.Context + } +} + +# Format the resource group name based on resource group naming recommendations and limitations. +$resourceGroupName = "rg-{0}-$baseName" -f ($ServiceDirectory -replace '[\\\/]', '-').Substring(0, [Math]::Min($ServiceDirectory.Length, 90 - $BaseName.Length - 4)).Trim('-') + +Log "Deleting resource group '${resourceGroupName}'" +if (Remove-AzResourceGroup -Name "${resourceGroupName}" -Force:$Force) { + Write-Verbose "Successfully deleted resource group '${resourceGroupName}'" +} + +$exitActions.Invoke() + +<# +.SYNOPSIS +Deletes the resource group deployed for a service directory from Azure. + +.DESCRIPTION +Removes a resource group and all its resources previously deployed for the specified $ServiceDirectory using New-TestResources.ps1. The $ServiceDirectory must match the previously specified value, e.g. 'keyvault' as shown in examples. + +If you are not currently logged into an account in the Az PowerShell module, you will be asked to log in with Connect-AzAccount. Alternatively, you (or a build pipeline) can pass $ProvisionerApplicationId and $ProvisionerApplicationSecret to authenticate a service principal with access to create resources. + +.PARAMETER BaseName +A name to use in the resource group and passed to the ARM template as 'baseName'. + +.PARAMETER ServiceDirectory +A directory under 'sdk' in the repository root - optionally with subdirectories specified - in which to discover ARM templates named 'test-resources.json'. + +.PARAMETER TenantId +The tenant ID of a service principal when a provisioner is specified. + +.PARAMETER ProvisionerApplicationId +A service principal ID to provision test resources when a provisioner is specified. + +.PARAMETER ProvisionerApplicationSecret +A service principal secret (password) to provision test resources when a provisioner is specified. + +.PARAMETER NoProvisionerAutoSave +Do not save credentials for the provisioner in the current process. + +.PARAMETER Force +Force creation of resources instead of being prompted. + +.EXAMPLE +./Remove-Template.ps1 -BaseName uuid123 -ServiceDirectory keyvault -Force + +Use the currently logged-in account to delete the resource group provisioned by the sdk/keyvault/test-resources.json ARM template. + +.LINK +Remove-TestResources.ps1 +#> diff --git a/eng/Update-Snippets.ps1 b/eng/Update-Snippets.ps1 index a1ebf1139360..6317048742e0 100644 --- a/eng/Update-Snippets.ps1 +++ b/eng/Update-Snippets.ps1 @@ -11,4 +11,6 @@ if ($ServiceDirectory) { $root += '/' + $ServiceDirectory } -dotnet run -p $generatorProject -b "$root" +$repoRoot = Resolve-Path "$root" + +dotnet run -p $generatorProject -b "$repoRoot" diff --git a/sdk/keyvault/test-resources-post.ps1 b/sdk/keyvault/test-resources-post.ps1 new file mode 100644 index 000000000000..9c76705357d7 --- /dev/null +++ b/sdk/keyvault/test-resources-post.ps1 @@ -0,0 +1,34 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# IMPORTANT: Do not invoke this file directly. Please instead run eng/New-TestResources.ps1 from the repository root. + +#Requires -Version 6.0 +#Requires -PSEdition Core + +# Use same parameter names as declared in eng/New-TestResources.ps1 (assume validation therein). +[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] +param ( + [Parameter(Mandatory = $true)] + [string] $ResourceGroupName, + + [Parameter()] + [string] $TestApplicationOid, + + [Parameter()] + [hashtable] $DeploymentOutputs, + + # Captures any arguments from eng/New-TestResources.ps1 not declared here (no parameter errors). + [Parameter(ValueFromRemainingArguments = $true)] + $RemainingArguments +) + +function Log($Message) { + Write-Host ('{0} - {1}' -f [DateTime]::Now.ToLongTimeString(), $Message) +} + +Write-Verbose "Example post-deployment script for Azure Key Vault in resource group '$ResourceGroupName' accessible to $TestApplicationOid." +if ($DeploymentOutputs) { + $names = $DeploymentOutputs.Keys -join ', ' + Log "Output variable names: $names" +} diff --git a/sdk/keyvault/test-resources-pre.ps1 b/sdk/keyvault/test-resources-pre.ps1 new file mode 100644 index 000000000000..71e224e6d25f --- /dev/null +++ b/sdk/keyvault/test-resources-pre.ps1 @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# IMPORTANT: Do not invoke this file directly. Please instead run eng/New-TestResources.ps1 from the repository root. + +#Requires -Version 6.0 +#Requires -PSEdition Core + +# Use same parameter names as declared in eng/New-TestResources.ps1 (assume validation therein). +[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] +param ( + [Parameter(Mandatory = $true)] + [string] $ResourceGroupName, + + [Parameter()] + [string] $TestApplicationOid, + + # Captures any arguments from eng/New-TestResources.ps1 not declared here (no parameter errors). + [Parameter(ValueFromRemainingArguments = $true)] + $RemainingArguments +) + +Write-Verbose "Example pre-deployment script for Azure Key Vault in resource group '$ResourceGroupName' accessible to $TestApplicationOid." diff --git a/sdk/keyvault/test-resources.json b/sdk/keyvault/test-resources.json new file mode 100644 index 000000000000..d0f9545f5356 --- /dev/null +++ b/sdk/keyvault/test-resources.json @@ -0,0 +1,140 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "baseName": { + "type": "string", + "defaultValue": "[resourceGroup().name]", + "metadata": { + "description": "The base resource name." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "72f988bf-86f1-41af-91ab-2d7cd011db47", + "metadata": { + "description": "The tenant ID to which the application and resources belong." + } + }, + "testApplicationId": { + "type": "string", + "metadata": { + "description": "The application client ID used to run tests." + } + }, + "testApplicationSecret": { + "type": "string", + "metadata": { + "description": "The application client secret used to run tests." + } + }, + "testApplicationOid": { + "type": "string", + "defaultValue": "b3653439-8136-4cd5-aac3-2a9460871ca6", + "metadata": { + "description": "The client OID to grant access to test resources." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The location of the resource. By default, this is the same as the resource group." + } + } + }, + "variables": { + "azureKeyVaultUrl": "[format('https://{0}.vault.azure.net', parameters('baseName'))]" + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2016-10-01", + "name": "[parameters('baseName')]", + "location": "[parameters('location')]", + "properties": { + "sku": { + "family": "A", + "name": "premium" + }, + "tenantId": "[parameters('tenantId')]", + "accessPolicies": [ + { + "tenantId": "[parameters('tenantId')]", + "objectId": "[parameters('testApplicationOid')]", + "permissions": { + "keys": [ + "get", + "list", + "update", + "create", + "import", + "delete", + "recover", + "backup", + "restore", + "decrypt", + "encrypt", + "unwrapKey", + "wrapKey", + "verify", + "sign", + "purge" + ], + "secrets": [ + "get", + "list", + "set", + "delete", + "recover", + "backup", + "restore", + "purge" + ], + "certificates": [ + "get", + "list", + "update", + "create", + "import", + "delete", + "recover", + "backup", + "restore", + "managecontacts", + "manageissuers", + "getissuers", + "listissuers", + "setissuers", + "deleteissuers", + "purge" + ] + } + } + ], + "enabledForDeployment": false, + "enabledForDiskEncryption": false, + "enabledForTemplateDeployment": false, + "enableSoftDelete": true + } + } + ], + "outputs": { + "AZURE_TENANT_ID": { + "type": "string", + "value": "[parameters('tenantId')]" + }, + "AZURE_CLIENT_ID": { + "type": "string", + "value": "[parameters('testApplicationId')]" + }, + "AZURE_CLIENT_SECRET": { + "type": "string", + "value": "[parameters('testApplicationSecret')]" + }, + "AZURE_KEYVAULT_URL": { + "type": "string", + "value": "[variables('azureKeyVaultUrl')]" + } + } +}