Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error handling for RG deployment validation: #8849

Merged
merged 1 commit into from
Apr 4, 2019
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -116,69 +116,78 @@ protected ResourceWithParameterCmdletBase()
[ValidateNotNullOrEmpty]
public string TemplateUri { get; set; }

[Parameter(Mandatory = false, HelpMessage = "Skips the PowerShell dynamic parameter processing that checks if the provided template parameter contains all necessary parameters used by the template. " +
"This check would prompt the user to provide a value for the missing parameters, but providing the -SkipTemplateParameterPrompt will ignore this prompt and " +
"error out immediately if a parameter was found not to be bound in the template. For non-interactive scripts, -SkipTemplateParameterPrompt can be provided " +
"to provide a better error message in the case where not all required parameters are satisfied.")]
public SwitchParameter SkipTemplateParameterPrompt { get; set; }

public object GetDynamicParameters()
{
if (TemplateObject != null &&
TemplateObject != templateObject)
{
templateObject = TemplateObject;
if (string.IsNullOrEmpty(TemplateParameterUri))
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
TemplateObject,
TemplateParameterObject,
this.ResolvePath(TemplateParameterFile),
MyInvocation.MyCommand.Parameters.Keys.ToArray());
}
else
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
TemplateObject,
TemplateParameterObject,
TemplateParameterUri,
MyInvocation.MyCommand.Parameters.Keys.ToArray());
}
}
else if (!string.IsNullOrEmpty(TemplateFile) &&
!TemplateFile.Equals(templateFile, StringComparison.OrdinalIgnoreCase))
if (!this.IsParameterBound(c => c.SkipTemplateParameterPrompt))
{
templateFile = TemplateFile;
if (string.IsNullOrEmpty(TemplateParameterUri))
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
this.ResolvePath(TemplateFile),
TemplateParameterObject,
this.ResolvePath(TemplateParameterFile),
MyInvocation.MyCommand.Parameters.Keys.ToArray());
}
else
if (TemplateObject != null &&
TemplateObject != templateObject)
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
this.ResolvePath(TemplateFile),
TemplateParameterObject,
TemplateParameterUri,
MyInvocation.MyCommand.Parameters.Keys.ToArray());
templateObject = TemplateObject;
if (string.IsNullOrEmpty(TemplateParameterUri))
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
TemplateObject,
TemplateParameterObject,
this.ResolvePath(TemplateParameterFile),
MyInvocation.MyCommand.Parameters.Keys.ToArray());
}
else
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
TemplateObject,
TemplateParameterObject,
TemplateParameterUri,
MyInvocation.MyCommand.Parameters.Keys.ToArray());
}
}
}
else if (!string.IsNullOrEmpty(TemplateUri) &&
!TemplateUri.Equals(templateUri, StringComparison.OrdinalIgnoreCase))
{
templateUri = TemplateUri;
if (string.IsNullOrEmpty(TemplateParameterUri))
else if (!string.IsNullOrEmpty(TemplateFile) &&
!TemplateFile.Equals(templateFile, StringComparison.OrdinalIgnoreCase))
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
TemplateUri,
TemplateParameterObject,
this.ResolvePath(TemplateParameterFile),
MyInvocation.MyCommand.Parameters.Keys.ToArray());
templateFile = TemplateFile;
if (string.IsNullOrEmpty(TemplateParameterUri))
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
this.ResolvePath(TemplateFile),
TemplateParameterObject,
this.ResolvePath(TemplateParameterFile),
MyInvocation.MyCommand.Parameters.Keys.ToArray());
}
else
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
this.ResolvePath(TemplateFile),
TemplateParameterObject,
TemplateParameterUri,
MyInvocation.MyCommand.Parameters.Keys.ToArray());
}
}
else
else if (!string.IsNullOrEmpty(TemplateUri) &&
!TemplateUri.Equals(templateUri, StringComparison.OrdinalIgnoreCase))
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
TemplateUri,
TemplateParameterObject,
TemplateParameterUri,
MyInvocation.MyCommand.Parameters.Keys.ToArray());
templateUri = TemplateUri;
if (string.IsNullOrEmpty(TemplateParameterUri))
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
TemplateUri,
TemplateParameterObject,
this.ResolvePath(TemplateParameterFile),
MyInvocation.MyCommand.Parameters.Keys.ToArray());
}
else
{
dynamicParameters = TemplateUtility.GetTemplateParametersFromFile(
TemplateUri,
TemplateParameterObject,
TemplateParameterUri,
MyInvocation.MyCommand.Parameters.Keys.ToArray());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public class TestAzureResourceGroupDeploymentCmdlet : ResourceWithParameterCmdle
[Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, HelpMessage = "Rollback to the successful deployment with the given name in the resource group, should not be used if -RollbackToLastDeployment is used.")]
public string RollBackDeploymentName { get; set; }

public string DeploymentName { get; set; }

public TestAzureResourceGroupDeploymentCmdlet()
{
this.Mode = DeploymentMode.Incremental;
Expand All @@ -57,6 +59,7 @@ public override void ExecuteCmdlet()

PSDeploymentCmdletParameters parameters = new PSDeploymentCmdletParameters()
{
DeploymentName = DeploymentName ?? Guid.NewGuid().ToString(),
ResourceGroupName = ResourceGroupName,
TemplateFile = TemplateUri ?? this.ResolvePath(TemplateFile),
TemplateObject = TemplateObject,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
using ProjectResources = Microsoft.Azure.Commands.ResourceManager.Cmdlets.Properties.Resources;
using Microsoft.Azure.Commands.ResourceManager.Common.Paging;
using System.Management.Automation;
using Microsoft.Rest;

namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkClient
{
Expand Down Expand Up @@ -465,11 +466,42 @@ private Deployment CreateBasicDeployment(PSDeploymentCmdletParameters parameters

private TemplateValidationInfo CheckBasicDeploymentErrors(string resourceGroup, string deploymentName, Deployment deployment)
{
DeploymentValidateResult validationResult = resourceGroup != null
? ResourceManagementClient.Deployments.Validate(resourceGroup, deploymentName, deployment)
: ResourceManagementClient.Deployments.ValidateAtSubscriptionScope(deploymentName, deployment);
try
{
DeploymentValidateResult validationResult = resourceGroup != null
? ResourceManagementClient.Deployments.Validate(resourceGroup, deploymentName, deployment)
: ResourceManagementClient.Deployments.ValidateAtSubscriptionScope(deploymentName, deployment);

return new TemplateValidationInfo(validationResult);
}
catch (Exception ex)
{
var error = HandleError(ex).FirstOrDefault();
return new TemplateValidationInfo(new DeploymentValidateResult(error));
}
}

private List<ResourceManagementErrorWithDetails> HandleError(Exception ex)
{
if (ex == null)
{
return null;
}

ResourceManagementErrorWithDetails error = null;
var innerException = HandleError(ex.InnerException);
if (ex is CloudException)
{
var cloudEx = ex as CloudException;
error = new ResourceManagementErrorWithDetails(cloudEx.Body?.Code, cloudEx.Body?.Message, cloudEx.Body?.Target, innerException);
}
else
{
error = new ResourceManagementErrorWithDetails(null, ex.Message, null, innerException);
}

return new List<ResourceManagementErrorWithDetails> { error };

return new TemplateValidationInfo(validationResult);
}

private IPage<DeploymentOperation> ListDeploymentOperations(string resourceGroupName, string deploymentName)
Expand Down Expand Up @@ -1071,7 +1103,8 @@ public virtual void CancelDeployment(string resourceGroup, string deploymentName
public virtual List<PSResourceManagerError> ValidateDeployment(PSDeploymentCmdletParameters parameters, DeploymentMode deploymentMode)
{
Deployment deployment = CreateBasicDeployment(parameters, deploymentMode, null);
TemplateValidationInfo validationInfo = CheckBasicDeploymentErrors(parameters.ResourceGroupName, Guid.NewGuid().ToString(), deployment);
var deploymentName = GenerateDeploymentName(parameters);
TemplateValidationInfo validationInfo = CheckBasicDeploymentErrors(parameters.ResourceGroupName, deploymentName, deployment);

if (validationInfo.Errors.Count == 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public TemplateValidationInfo(DeploymentValidateResult validationResult)
Errors = new List<ResourceManagementErrorWithDetails>();
RequiredProviders = new List<Provider>();


if (validationResult.Error != null)
{
Errors.Add(validationResult.Error);
Expand Down
100 changes: 99 additions & 1 deletion src/Resources/Resources.Test/ScenarioTests/Common.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ function New-AzADSpCredentialWithId
)

$profile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
$cmdlet = New-Object -TypeName Microsoft.Azure.Commands.ActiveDirectory.NewAzureADSpCredentialCommand
$cmdlet = New-Object -TypeName Microsoft.Azure.Commands.ActiveDirectory.NewAzureADSpCredentialCommand
$cmdlet.DefaultProfile = $profile
$cmdlet.CommandRuntime = $PSCmdlet.CommandRuntime

Expand Down Expand Up @@ -297,5 +297,103 @@ function New-AzADSpCredentialWithId
$cmdlet.KeyId = $KeyId
}

$cmdlet.ExecuteCmdlet()
}

function Test-AzResourceGroupDeploymentWithName
{
[CmdletBinding()]
param(
[string] [Parameter()] $DeploymentName,
[string] [Parameter()] $ResourceGroupName,
[string] [Parameter()] $RollBackDeploymentName,
[string] [Parameter()] $TemplateFile,
[string] [Parameter()] $TemplateUri,
[string] [Parameter()] $TemplateParameterFile,
[string] [Parameter()] $TemplateParameterUri,
[string] [Parameter()] $ApiVersion,
[switch] [Parameter()] $RollbackToLastDeployment,
[switch] [Parameter()] $SkipTemplateParameterPrompt,
[switch] [Parameter()] $Pre,
[hashtable] [Parameter()] $TemplateObject,
[hashtable] [Parameter()] $TemplateParameterObject,
[Microsoft.Azure.Management.ResourceManager.Models.DeploymentMode] [Parameter()] $Mode
)

$profile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
$cmdlet = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.TestAzureResourceGroupDeploymentCmdlet
$cmdlet.DefaultProfile = $profile
$cmdlet.CommandRuntime = $PSCmdlet.CommandRuntime

if (-not ([string]::IsNullOrEmpty($DeploymentName)))
{
$cmdlet.DeploymentName = $DeploymentName
}

if (-not ([string]::IsNullOrEmpty($ResourceGroupName)))
{
$cmdlet.ResourceGroupName = $ResourceGroupName
}

if (-not ([string]::IsNullOrEmpty($RollBackDeploymentName)))
{
$cmdlet.RollBackDeploymentName = $RollBackDeploymentName
}

if (-not ([string]::IsNullOrEmpty($TemplateFile)))
{
$cmdlet.TemplateFile = $TemplateFile
}

if (-not ([string]::IsNullOrEmpty($TemplateUri)))
{
$cmdlet.TemplateUri = $TemplateUri
}

if (-not ([string]::IsNullOrEmpty($TemplateParameterFile)))
{
$cmdlet.TemplateParameterFile = $TemplateParameterFile
}

if (-not ([string]::IsNullOrEmpty($TemplateParameterUri)))
{
$cmdlet.TemplateParameterUri = $TemplateParameterUri
}

if (-not ([string]::IsNullOrEmpty($ApiVersion)))
{
$cmdlet.ApiVersion = $ApiVersion
}

if ($RollbackToLastDeployment.IsPresent)
{
$cmdlet.RollbackToLastDeployment = $true
}

if ($SkipTemplateParameterPrompt.IsPresent)
{
$cmdlet.SkipTemplateParameterPrompt = $true
}

if ($Pre.IsPresent)
{
$cmdlet.Pre = $true
}

if ($TemplateObject -ne $null)
{
$cmdlet.TemplateObject = $TemplateObject
}

if ($TemplateParameterObject -ne $null)
{
$cmdlet.TemplateParameterObject = $TemplateParameterObject
}

if ($Mode -ne $null)
{
$cmdlet.Mode = $Mode
}

$cmdlet.ExecuteCmdlet()
}
7 changes: 7 additions & 0 deletions src/Resources/Resources.Test/ScenarioTests/DeploymentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ public void TestNewDeploymentFromTemplateObject()
TestRunner.RunTestScript("Test-NewDeploymentFromTemplateObject");
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void TestTestResourceGroupDeploymentErrors()
{
TestRunner.RunTestScript("Test-TestResourceGroupDeploymentErrors");
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void TestNestedDeploymentFromTemplateFile()
Expand Down
33 changes: 33 additions & 0 deletions src/Resources/Resources.Test/ScenarioTests/DeploymentTests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,39 @@ function Test-NewDeploymentFromTemplateObject
}
}

function Test-TestResourceGroupDeploymentErrors
{
# Catch exception when resource group doesn't exist
$rgname = "unknownresourcegroup"
$deploymentName = Get-ResourceName
$result = Test-AzResourceGroupDeploymentWithName -DeploymentName $deploymentName -ResourceGroupName $rgname -TemplateFile sampleDeploymentTemplate.json -TemplateParameterFile sampleDeploymentTemplateParams.json
Write-Debug "$result"
Assert-NotNull $result
Assert-AreEqual "ResourceGroupNotFound" $result.Code
Assert-AreEqual "Resource group '$rgname' could not be found." $result.Message

# Setup
$rgname = Get-ResourceGroupName
$rname = Get-ResourceName
$rglocation = "West US 2"

try
{
# Test
# Catch exception when parameter template is missing
New-AzResourceGroup -Name $rgname -Location $rglocation
$result = Test-AzResourceGroupDeploymentWithName -DeploymentName $deploymentName -ResourceGroupName $rgname -TemplateFile sampleDeploymentTemplate.json -SkipTemplateParameterPrompt
Assert-NotNull $result
Assert-AreEqual "InvalidTemplate" $result.Code
Assert-StartsWith "Deployment template validation failed" $result.Message
}
finally
{
# Cleanup
Clean-ResourceGroup $rgname
}
}

<#
.SYNOPSIS
Tests cross resource group deployment via template file.
Expand Down
Loading