Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 64 additions & 28 deletions eng/common/TestResources/New-TestResources.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ param (

$wellKnownTMETenants = @('70a036f6-8e4d-4615-bad6-149c02e7720d')

# People keep passing this legacy parameter. Throw an error to save them future keystrokes
if ($NewTestResourcesRemainingArguments -like '*UserAuth*') {
throw "The -UserAuth parameter is deprecated and is now the default behavior"
}

if (!$ServicePrincipalAuth) {
# Clear secrets if not using Service Principal auth. This prevents secrets
# from being passed to pre- and post-scripts.
Expand Down Expand Up @@ -174,7 +179,7 @@ try {
}
Write-Verbose "Overriding test resources search directory to '$root'"
}

$templateFiles = @()

"$ResourceType-resources.json", "$ResourceType-resources.bicep" | ForEach-Object {
Expand All @@ -198,7 +203,7 @@ try {

# returns empty string if $ServiceDirectory is not set
$serviceName = GetServiceLeafDirectoryName $ServiceDirectory

# in ci, random names are used
# in non-ci, without BaseName, ResourceGroupName or ServiceDirectory, all invocations will
# generate the same resource group name and base name for a given user
Expand Down Expand Up @@ -267,13 +272,13 @@ try {
if ($context.Tenant.Name -like '*TME*') {
if ($currentSubscriptionId -ne '4d042dc6-fe17-4698-a23f-ec6a8d1e98f4') {
Log "Attempting to select subscription 'Azure SDK Test Resources - TME (4d042dc6-fe17-4698-a23f-ec6a8d1e98f4)'"
$null = Select-AzSubscription -Subscription '4d042dc6-fe17-4698-a23f-ec6a8d1e98f4' -ErrorAction Ignore
$null = Select-AzSubscription -Subscription '4d042dc6-fe17-4698-a23f-ec6a8d1e98f4' -ErrorAction Ignore -WarningAction Ignore
# Update the context.
$context = Get-AzContext
}
} elseif ($currentSubcriptionId -ne 'faa080af-c1d8-40ad-9cce-e1a450ca5b57') {
Log "Attempting to select subscription 'Azure SDK Developer Playground (faa080af-c1d8-40ad-9cce-e1a450ca5b57)'"
$null = Select-AzSubscription -Subscription 'faa080af-c1d8-40ad-9cce-e1a450ca5b57' -ErrorAction Ignore
$null = Select-AzSubscription -Subscription 'faa080af-c1d8-40ad-9cce-e1a450ca5b57' -ErrorAction Ignore -WarningAction Ignore
# Update the context.
$context = Get-AzContext
}
Expand Down Expand Up @@ -305,7 +310,7 @@ try {
}
}

# This needs to happen after we set the TenantId but before we use the ResourceGroupName
# This needs to happen after we set the TenantId but before we use the ResourceGroupName
if ($wellKnownTMETenants.Contains($TenantId)) {
# Add a prefix to the resource group name to avoid flagging the usages of local auth
# See details at https://eng.ms/docs/products/onecert-certificates-key-vault-and-dsms/key-vault-dsms/certandsecretmngmt/credfreefaqs#how-can-i-disable-s360-reporting-when-testing-customer-facing-3p-features-that-depend-on-use-of-unsafe-local-auth
Expand Down Expand Up @@ -353,15 +358,19 @@ try {
# Make sure the provisioner OID is set so we can pass it through to the deployment.
if (!$ProvisionerApplicationId -and !$ProvisionerApplicationOid) {
if ($context.Account.Type -eq 'User') {
# Support corp tenant and TME tenant user id lookups
$user = Get-AzADUser -Mail $context.Account.Id
if ($null -eq $user -or !$user.Id) {
$user = Get-AzADUser -UserPrincipalName $context.Account.Id
# Calls to graph API in corp tenant get blocked by conditional access policy now
# but not in TME. For corp tenant we get the user's id from the login context
# but for TME it is different so we have to source it from graph
$userAccountId = if ($wellKnownTMETenants.Contains($TenantId)) {
(Get-AzADUser -SignedIn).Id
} else {
# HomeAccountId format is '<object id>.<tenant id>'
(Get-AzContext).Account.ExtendedProperties.HomeAccountId.Split('.')[0]
}
if ($null -eq $user -or !$user.Id) {
if ($null -eq $userAccountId) {
throw "Failed to find entra object ID for the current user"
}
$ProvisionerApplicationOid = $user.Id
$ProvisionerApplicationOid = $userAccountId
} elseif ($context.Account.Type -eq 'ServicePrincipal') {
$sp = Get-AzADServicePrincipal -ApplicationId $context.Account.Id
$ProvisionerApplicationOid = $sp.Id
Expand Down Expand Up @@ -428,20 +437,25 @@ try {

if (!$CI -and !$ServicePrincipalAuth) {
if ($TestApplicationId) {
Write-Warning "The specified TestApplicationId '$TestApplicationId' will be ignored when -ServicePrincipalAutth is not set."
Write-Warning "The specified TestApplicationId '$TestApplicationId' will be ignored when -ServicePrincipalAuth is not set."
}

# Support corp tenant and TME tenant user id lookups
$userAccount = (Get-AzADUser -Mail (Get-AzContext).Account.Id)
if ($null -eq $userAccount -or !$userAccount.Id) {
$userAccount = (Get-AzADUser -UserPrincipalName (Get-AzContext).Account)
$userAccountName = (Get-AzContext).Account.Id
# HomeAccountId format is '<object id>.<tenant id>'
# Calls to graph API in corp tenant get blocked by conditional access policy now
# but not in TME. For corp tenant we get the user's id from the login context
# but for TME it is different so we have to source it from graph
$userAccountId = if ($wellKnownTMETenants.Contains($TenantId)) {
(Get-AzADUser -SignedIn).Id
} else {
# HomeAccountId format is '<object id>.<tenant id>'
(Get-AzContext).Account.ExtendedProperties.HomeAccountId.Split('.')[0]
}
if ($null -eq $userAccount -or !$userAccount.Id) {
if ($null -eq $userAccountId) {
throw "Failed to find entra object ID for the current user"
}
$TestApplicationOid = $userAccount.Id
$TestApplicationOid = $userAccountId
$TestApplicationId = $testApplicationOid
$userAccountName = $userAccount.UserPrincipalName
Log "User authentication with user '$userAccountName' ('$TestApplicationId') will be used."
}
# If user has specified -ServicePrincipalAuth
Expand Down Expand Up @@ -613,22 +627,44 @@ try {
}
Log $msg

$deployment = Retry {
New-AzResourceGroupDeployment `
# Run a first pass outside of Retry to fail fast for
# template validation errors that won't be fixed with retries.
# Only run Test-AzResourceGroupDeployment after error because it can
# take a while for large templates even during success cases.
try {
$deployment = New-AzResourceGroupDeployment `
-Name $BaseName `
-ResourceGroupName $resourceGroup.ResourceGroupName `
-TemplateFile $templateFile.jsonFilePath `
-TemplateParameterObject $templateFileParameters `
-Force:$Force
} catch {
# Throw if we hit a template validation error, otherwise proceed
if ($_.Exception.Message -like '*InvalidTemplateDeployment*') {
$validation = Test-AzResourceGroupDeployment `
-ResourceGroupName $resourceGroup.ResourceGroupName `
-TemplateFile $templateFile.jsonFilePath `
-TemplateParameterObject $templateFileParameters

HandleTemplateDeploymentError $validation
throw
}
}

if (!$deployment -or $deployment.ProvisioningState -ne 'Succeeded') {
Write-Warning "Initial deployment attempt failed, retrying..."
$deployment = Retry -Attempts 4 -Action {
New-AzResourceGroupDeployment `
-Name $BaseName `
-ResourceGroupName $resourceGroup.ResourceGroupName `
-TemplateFile $templateFile.jsonFilePath `
-TemplateParameterObject $templateFileParameters `
-Force:$Force
}
}

if ($deployment.ProvisioningState -ne 'Succeeded') {
Write-Host "Deployment '$($deployment.DeploymentName)' has state '$($deployment.ProvisioningState)' with CorrelationId '$($deployment.CorrelationId)'. Exiting..."
Write-Host @'
#####################################################
# For help debugging live test provisioning issues, #
# see http://aka.ms/azsdk/engsys/live-test-help #
#####################################################
'@
HandleDeploymentFailure $deployment
exit 1
}

Expand Down
77 changes: 54 additions & 23 deletions eng/common/TestResources/TestResources-Helpers.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,6 @@ function LintBicepFile([string] $path) {
}

# Work around lack of config file override: https://github.com/Azure/bicep/issues/5013
$output = bicep lint $path 2>&1

if ($useBicepCli) {
$output = bicep lint $path 2>&1
} else {
Expand Down Expand Up @@ -264,9 +262,10 @@ function SetDeploymentOutputs(
) {
$deploymentEnvironmentVariables = $environmentVariables.Clone()
$deploymentOutputs = BuildDeploymentOutputs $serviceName $azContext $deployment $deploymentEnvironmentVariables
$isBicep = $templateFile.originalFilePath -and $templateFile.originalFilePath.EndsWith(".bicep")

if ($OutFile) {
if ($IsWindows -and $Language -eq 'dotnet') {
# Azure SDK for .NET on Windows uses DPAPI-encrypted, JSON-encoded environment variables.
if ($OutFile -and $IsWindows -and $Language -eq 'dotnet') {
$outputFile = "$($templateFile.originalFilePath).env"

$environmentText = $deploymentOutputs | ConvertTo-Json;
Expand All @@ -276,29 +275,29 @@ function SetDeploymentOutputs(
Set-Content $outputFile -Value $protectedBytes -AsByteStream -Force

Write-Host "Test environment settings`n$environmentText`nstored into encrypted $outputFile"
}
# Any Bicep template in a repo that has opted into .env files.
elseif ($OutFile -and $isBicep) {
$bicepTemplateFile = $templateFile.originalFilePath

# Make sure the file would not write secrets to .env file.
if (!(LintBicepFile $bicepTemplateFile)) {
Write-Error "$bicepTemplateFile may write secrets. No file written."
}
elseif ($templateFile.originalFilePath -and $templateFile.originalFilePath.EndsWith(".bicep")) {
$bicepTemplateFile = $templateFile.originalFilePath
$outputFile = $bicepTemplateFile | Split-Path | Join-Path -ChildPath '.env'

# Make sure the file would not write secrets to .env file.
if (!(LintBicepFile $bicepTemplateFile)) {
Write-Error "$bicepTemplateFile may write secrets. No file written."
# Make sure the file would be ignored.
git check-ignore -- "$outputFile" > $null
if ($?) {
$environmentText = foreach ($kv in $deploymentOutputs.GetEnumerator()) {
"$($kv.Key)=`"$($kv.Value)`""
}
$outputFile = $bicepTemplateFile | Split-Path | Join-Path -ChildPath '.env'

# Make sure the file would be ignored.
git check-ignore -- "$outputFile" > $null
if ($?) {
$environmentText = foreach ($kv in $deploymentOutputs.GetEnumerator()) {
"$($kv.Key)=`"$($kv.Value)`""
}

Set-Content $outputFile -Value $environmentText -Force
Write-Host "Test environment settings`n$environmentText`nstored in $outputFile"
}
else {
Write-Error "$outputFile is not ignored by .gitignore. No file written."
}
Set-Content $outputFile -Value $environmentText -Force
Write-Host "Test environment settings`n$environmentText`nstored in $outputFile"
}
else {
Write-Error "$outputFile is not ignored by .gitignore. No file written."
}
}
else {
Expand Down Expand Up @@ -349,3 +348,35 @@ function SetDeploymentOutputs(

return $deploymentEnvironmentVariables, $deploymentOutputs
}

function HandleTemplateDeploymentError($templateValidationResult) {
Write-Warning "Deployment template validation failed"

if (!$templateValidationResult.Details.Message) {
Write-Warning "Could not parse template validation error"
return
}

# Retrieve one or more messages then decode the strings for readability (remove quote escapes, fix link readability, etc.)
$parsedMessage = ($templateValidationResult.Details.Message -join "$([System.Environment]::NewLine)$([System.Environment]::NewLine)")
$parsedMessage = [System.Net.WebUtility]::UrlDecode($parsedMessage)

Write-Warning "#####################################################"
Write-Warning "######### TEMPLATE VALIDATION ERROR DETAILS #########"
Write-Warning "#####################################################"
Write-Host $parsedMessage
Write-Warning "#####################################################"
}

function HandleDeploymentFailure($deployment) {
Write-Host "Deployment '$($deployment.DeploymentName)' has state '$($deployment.ProvisioningState)' with CorrelationId '$($deployment.CorrelationId)'. Exiting..."
Write-Host @'
#####################################################
# For help debugging live test provisioning issues, #
# see http://aka.ms/azsdk/engsys/live-test-help #
#####################################################
'@
$queryTime = (Get-Date).AddMinutes(-10).ToString("o")
Write-Host "To check the activity log with the below command after waiting 2 minutes for propagation:"
Write-Host "(Get-AzActivityLog -CorrelationId '$($deployment.CorrelationId)' -StartTime '$queryTime').Properties.Content.statusMessage"
}
7 changes: 5 additions & 2 deletions eng/common/TestResources/deploy-test-resources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ parameters:
ServiceDirectory: ''
TestResourcesDirectory: ''
ArmTemplateParameters: '@{}'
AdditionalParameters: '@{}'
DeleteAfterHours: 8
Location: ''
EnvVars: {}
Expand Down Expand Up @@ -103,7 +104,8 @@ steps:
-Location '${{ parameters.Location }}' `
-DeleteAfterHours '${{ parameters.DeleteAfterHours }}' `
@subscriptionConfiguration `
-AdditionalParameters ${{ parameters.ArmTemplateParameters }} `
-ArmTemplateParameters ${{ parameters.ArmTemplateParameters }} `
-AdditionalParameters ${{ parameters.AdditionalParameters }} `
-AllowIpRanges ('$(azsdk-corp-net-ip-ranges)' -split ',') `
-SelfContainedPostScript $postScriptPath `
-CI `
Expand Down Expand Up @@ -148,7 +150,8 @@ steps:
-Location '${{ parameters.Location }}' `
-DeleteAfterHours '${{ parameters.DeleteAfterHours }}' `
@subscriptionConfiguration `
-AdditionalParameters ${{ parameters.ArmTemplateParameters }} `
-ArmTemplateParameters ${{ parameters.ArmTemplateParameters }} `
-AdditionalParameters ${{ parameters.AdditionalParameters }} `
-AllowIpRanges ('$(azsdk-corp-net-ip-ranges)' -split ',') `
-CI `
-ServicePrincipalAuth `
Expand Down
Loading