зеркало из
1
0
Форкнуть 0

Nest sub-resources resources for analysis #746 (#757)

This commit is contained in:
Bernie White 2021-05-13 01:36:05 +10:00 коммит произвёл GitHub
Родитель 2d481222e9
Коммит db1a19f0c9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
17 изменённых файлов: 669 добавлений и 239 удалений

1
.vscode/settings.json поставляемый
Просмотреть файл

@ -32,6 +32,7 @@
"SKUs",
"VNET",
"VNETs",
"agentpool",
"cmdlet",
"cmdlets",
"endregion",

4
.vscode/tasks.json поставляемый
Просмотреть файл

@ -38,7 +38,9 @@
"label": "coverage",
"type": "shell",
"command": "Invoke-Build Test -CodeCoverage",
"problemMatcher": [ "$pester" ],
"problemMatcher": [
"$pester"
],
"presentation": {
"clear": true,
"panel": "dedicated"

Просмотреть файл

@ -7,6 +7,13 @@ See [troubleshooting guide] for a workaround to this issue.
## Unreleased
- General improvements:
- Automatically nest template sub-resources for analysis. [#746](https://github.com/Microsoft/PSRule.Rules.Azure/issues/746)
- Sub-resources such as diagnostic logs or configurations are automatically nested.
- Automatic nesting a resource requires:
- The parent resource is defined in the same template.
- The sub-resource depends on the parent resource.
## v1.3.2
What's changed since v1.3.1:

Просмотреть файл

@ -52,6 +52,10 @@ Currently the following limitations apply:
- Nested templates are expanded, external templates are not.
- Deployment resources that link to an external template are returned as a resource.
- Sub-resources such as diagnostic logs or configurations are automatically nested.
Automatic nesting a sub-resource requires:
- The parent resource is defined in the same template.
- The sub-resource depends on the parent resource.
- The `pickZones` template function is not supported.
- The `environment` template function always returns values for Azure public cloud.
- References to Key Vault secrets are not expanded.

Просмотреть файл

@ -172,7 +172,11 @@ task PSRule NuGet, {
if ($Null -eq (Get-InstalledModule -Name PSRule -MinimumVersion 1.3.0 -ErrorAction Ignore)) {
Install-Module -Name PSRule -Repository PSGallery -MinimumVersion 1.3.0 -Scope CurrentUser -Force;
}
if ($Null -eq (Get-InstalledModule -Name PSRule.Rules.MSFT.OSS -MinimumVersion 0.1.0 -AllowPrerelease -ErrorAction Ignore)) {
Install-Module -Name PSRule.Rules.MSFT.OSS -Repository PSGallery -MinimumVersion 0.1.0 -AllowPrerelease -Scope CurrentUser -Force;
}
Import-Module -Name PSRule -Verbose:$False;
Import-Module -Name PSRule.Rules.MSFT.OSS -Verbose:$False;
}
# Synopsis: Install PSDocs
@ -314,7 +318,7 @@ task Rules PSRule, {
ErrorAction = 'Stop'
}
Import-Module (Join-Path -Path $PWD -ChildPath out/modules/PSRule.Rules.Azure) -Force;
Assert-PSRule @assertParams -InputPath $PWD -Format File -OutputPath reports/ps-rule-file.xml;
Assert-PSRule @assertParams -InputPath $PWD -Module PSRule.Rules.MSFT.OSS -Format File -OutputPath reports/ps-rule-file.xml;
$rules = Get-PSRule -Module PSRule.Rules.Azure;
$rules | Assert-PSRule @assertParams -OutputPath reports/ps-rule-file2.xml;

Просмотреть файл

@ -3,6 +3,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq;
namespace PSRule.Rules.Azure
{
@ -38,6 +39,8 @@ namespace PSRule.Rules.Azure
internal static class JsonExtensions
{
private const string FIELD_DEPENDSON = "dependsOn";
internal static IJsonLineInfo TryLineInfo(this JToken token)
{
if (token == null)
@ -75,5 +78,26 @@ namespace PSRule.Rules.Azure
if (annotation != null)
token.AddAnnotation(annotation);
}
internal static void UseProperty<TValue>(this JObject o, string propertyName, out TValue value) where TValue : JToken, new()
{
if (!o.TryGetValue(propertyName, System.StringComparison.OrdinalIgnoreCase, out JToken v))
{
value = new TValue();
o.Add(propertyName, value);
return;
}
value = (TValue)v;
}
internal static bool TryGetDependencies(this JObject resource, out string[] dependencies)
{
dependencies = null;
if (!(resource.ContainsKey(FIELD_DEPENDSON) && resource[FIELD_DEPENDSON] is JArray d && d.Count > 0))
return false;
dependencies = d.Values<string>().ToArray();
return true;
}
}
}

Просмотреть файл

@ -91,6 +91,8 @@ namespace PSRule.Rules.Azure.Data.Template
private readonly Stack<JObject> _Deployment;
private readonly ExpressionFactory _ExpressionFactory;
private readonly ExpressionBuilder _ExpressionBuilder;
private readonly List<ResourceValue> _Resources;
private readonly Dictionary<string, ResourceValue> _ResourceIds;
private JObject _Parameters;
private Dictionary<string, ResourceProvider> _Providers;
@ -98,7 +100,8 @@ namespace PSRule.Rules.Azure.Data.Template
internal TemplateContext()
{
Resources = new List<JObject>();
_Resources = new List<ResourceValue>();
_ResourceIds = new Dictionary<string, ResourceValue>(StringComparer.OrdinalIgnoreCase);
Parameters = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
Variables = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
CopyIndex = new CopyIndexStore();
@ -120,8 +123,6 @@ namespace PSRule.Rules.Azure.Data.Template
ResourceGroup = resourceGroup;
}
public List<JObject> Resources { get; }
private Dictionary<string, object> Parameters { get; }
private Dictionary<string, object> Variables { get; }
@ -142,6 +143,48 @@ namespace PSRule.Rules.Azure.Data.Template
return _ExpressionBuilder.Build(s);
}
public void AddResource(JObject resource)
{
if (resource == null)
return;
GetResourceNameType(resource, out string[] nameParts, out string[] typeParts);
var resourceId = GetResourceId(nameParts, typeParts);
AddResource(new ResourceValue(resourceId, resource));
}
private void AddResource(ResourceValue resource)
{
_Resources.Add(resource);
_ResourceIds[resource.ResourceId] = resource;
}
public void AddResource(ResourceValue[] resource)
{
for (var i = 0; resource != null && i < resource.Length; i++)
AddResource(resource[i]);
}
public ResourceValue[] GetResources()
{
return _Resources.ToArray();
}
public void RemoveResource(ResourceValue resource)
{
_Resources.Remove(resource);
_ResourceIds.Remove(resource.ResourceId);
}
public bool TryGetResource(string resourceId, out ResourceValue resource)
{
if (_ResourceIds.TryGetValue(resourceId, out resource))
return true;
resource = null;
return false;
}
public void WriteDebug(string message, params object[] args)
{
if (Pipeline == null || Pipeline.Writer == null || string.IsNullOrEmpty(message) || !Pipeline.Writer.ShouldWriteDebug())
@ -163,6 +206,67 @@ namespace PSRule.Rules.Azure.Data.Template
}
}
private static string GetResourceId(string[] nameParts, string[] typeParts, int depth = -1)
{
if (depth == -1)
depth = nameParts.Length;
var name = new string[2 * depth + 1];
var nameIndex = 0;
var typeIndex = 0;
name[0] = typeParts[typeIndex++];
name[1] = typeParts[typeIndex++];
name[2] = nameParts[nameIndex++];
for (var i = 3; i < 2 * depth + 1; i = i + 2)
{
name[i] = typeParts[typeIndex++];
name[i + 1] = nameParts[nameIndex++];
}
return string.Join("/", name);
}
internal static bool TryParentResourceId(JObject resource, out string[] resourceId)
{
resourceId = null;
if (GetResourceScope(resource, out resourceId))
return true;
if (!GetResourceNameType(resource, out string[] nameParts, out string[] typeParts))
return false;
resourceId = new string[nameParts.Length - 1];
for (var i = 0; i < nameParts.Length - 1; i++)
resourceId[i] = GetResourceId(nameParts, typeParts, i + 1);
return true;
}
private static bool GetResourceNameType(JObject resource, out string[] nameParts, out string[] typeParts)
{
var name = resource[PROPERTY_NAME].Value<string>();
nameParts = name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
typeParts = null;
if (nameParts == null || nameParts.Length == 0)
return false;
var type = resource[PROPERTY_TYPE].Value<string>();
typeParts = type.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (typeParts == null || typeParts.Length != nameParts.Length + 1)
return false;
return true;
}
private static bool GetResourceScope(JObject resource, out string[] resourceId)
{
resourceId = null;
if (!resource.ContainsKey(PROPERTY_SCOPE))
return false;
resourceId = new string[] { resource[PROPERTY_SCOPE].Value<string>() };
return true;
}
private bool TryParameterValue(string parameterName, JObject parameter)
{
if (!parameter.ContainsKey(PROPERTY_VALUE))
@ -465,6 +569,19 @@ namespace PSRule.Rules.Azure.Data.Template
}
}
[DebuggerDisplay("{ResourceId}")]
internal sealed class ResourceValue
{
internal readonly string ResourceId;
internal readonly JObject Value;
internal ResourceValue(string resourceId, JObject value)
{
ResourceId = resourceId;
Value = value;
}
}
private abstract class LazyValue
{
public abstract object GetValue(TemplateContext context);
@ -517,7 +634,14 @@ namespace PSRule.Rules.Azure.Data.Template
public void Visit(TemplateContext context, string deploymentName, JObject template)
{
BeginTemplate(context, deploymentName, template);
Template(context, deploymentName, template);
EndTemplate(context, deploymentName, template);
}
protected virtual void BeginTemplate(TemplateContext context, string deploymentName, JObject template)
{
}
protected virtual void Template(TemplateContext context, string deploymentName, JObject template)
@ -540,7 +664,7 @@ namespace PSRule.Rules.Azure.Data.Template
Variables(context, variables);
if (TryArrayProperty(template, PROPERTY_RESOURCES, out JArray resources))
Resources(context, resources.Values<JObject>());
Resources(context, resources.Values<JObject>().ToArray());
if (TryObjectProperty(template, PROPERTY_OUTPUTS, out JObject outputs))
Outputs(context, outputs);
@ -551,6 +675,11 @@ namespace PSRule.Rules.Azure.Data.Template
}
}
protected virtual void EndTemplate(TemplateContext context, string deploymentName, JObject template)
{
}
protected virtual void Schema(TemplateContext context, string schema)
{
@ -641,13 +770,14 @@ namespace PSRule.Rules.Azure.Data.Template
#region Resources
protected virtual void Resources(TemplateContext context, IEnumerable<JObject> resources)
protected virtual void Resources(TemplateContext context, JObject[] resources)
{
if (resources == null)
if (resources == null || resources.Length == 0)
return;
foreach (var resource in resources)
ResourceOuter(context, resource);
resources = SortResources(context, resources);
for (var i = 0; i < resources.Length; i++)
ResourceOuter(context, resources[i]);
}
private void ResourceOuter(TemplateContext context, JObject resource)
@ -721,8 +851,8 @@ namespace PSRule.Rules.Azure.Data.Template
return false;
Template(deploymentContext, deploymentName, template);
if (deploymentContext != context && deploymentContext.Resources != null && deploymentContext.Resources.Count > 0)
context.Resources.AddRange(deploymentContext.Resources);
if (deploymentContext != context)
context.AddResource(deploymentContext.GetResources());
return true;
}
@ -1124,7 +1254,21 @@ namespace PSRule.Rules.Azure.Data.Template
/// </summary>
protected virtual void Emit(TemplateContext context, JObject resource)
{
context.Resources.Add(resource);
context.AddResource(resource);
}
protected virtual JObject[] SortResources(TemplateContext context, JObject[] resources)
{
//var source = new List<JObject>(resources);
//var result = new List<JObject>(resources.Length);
//for (var i = 0; i < resources.Length; i++)
//{
//}
//return result.ToArray();
return resources;
}
}
@ -1133,22 +1277,62 @@ namespace PSRule.Rules.Azure.Data.Template
/// </summary>
internal sealed class RuleDataExportVisitor : TemplateVisitor
{
private const string FIELD_DEPENDSON = "dependsOn";
private const string FIELD_COMMENTS = "comments";
private const string FIELD_APIVERSION = "apiVersion";
private const string FIELD_CONDITION = "condition";
private const string FIELD_RESOURCES = "resources";
private readonly List<JObject> _Resources;
private readonly Dictionary<string, JObject> _ResourceMap;
internal RuleDataExportVisitor()
{
_Resources = new List<JObject>();
_ResourceMap = new Dictionary<string, JObject>();
}
protected override void Resource(TemplateContext context, JObject resource)
{
// Remove resource properties that not required in rule data
if (resource.ContainsKey("apiVersion"))
resource.Remove("apiVersion");
if (resource.ContainsKey(FIELD_APIVERSION))
resource.Remove(FIELD_APIVERSION);
if (resource.ContainsKey("condition"))
resource.Remove("condition");
if (resource.ContainsKey(FIELD_CONDITION))
resource.Remove(FIELD_CONDITION);
if (resource.ContainsKey("comments"))
resource.Remove("comments");
if (resource.ContainsKey(FIELD_COMMENTS))
resource.Remove(FIELD_COMMENTS);
if (resource.ContainsKey("dependsOn"))
resource.Remove("dependsOn");
if (!resource.TryGetDependencies(out _))
resource.Remove(FIELD_DEPENDSON);
base.Resource(context, resource);
}
protected override void EndTemplate(TemplateContext context, string deploymentName, JObject template)
{
var resources = context.GetResources();
for (var i = 0; i < resources.Length; i++)
{
if (resources[i].Value.TryGetDependencies(out string[] dependencies))
{
resources[i].Value.Remove(FIELD_DEPENDSON);
if (TemplateContext.TryParentResourceId(resources[i].Value, out string[] parentResourceId))
{
for (var j = 0; j < parentResourceId.Length; j++)
{
if (context.TryGetResource(parentResourceId[j], out ResourceValue resource))
{
resource.Value.UseProperty(FIELD_RESOURCES, out JArray innerResources);
innerResources.Add(resources[i].Value);
context.RemoveResource(resources[i]);
}
}
}
}
}
base.EndTemplate(context, deploymentName, template);
}
}
}

Просмотреть файл

@ -218,8 +218,8 @@ namespace PSRule.Rules.Azure.Pipeline
var results = new List<PSObject>();
var serializer = new JsonSerializer();
serializer.Converters.Add(new PSObjectJsonConverter());
foreach (var resource in context.Resources)
results.Add(resource.ToObject<PSObject>(serializer));
foreach (var resource in context.GetResources())
results.Add(resource.Value.ToObject<PSObject>(serializer));
return results.ToArray();
}

Просмотреть файл

@ -14,8 +14,11 @@ Rule 'Azure.AKS.MinNodeCount' -Type 'Microsoft.ContainerService/managedClusters'
Rule 'Azure.AKS.Version' -Type 'Microsoft.ContainerService/managedClusters', 'Microsoft.ContainerService/managedClusters/agentPools' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
$minVersion = [Version]$Configuration.Azure_AKSMinimumVersion
if ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters') {
([Version]$TargetObject.Properties.kubernetesVersion) -ge $minVersion
Reason ($LocalizedData.AKSVersion -f $TargetObject.Properties.kubernetesVersion);
$Assert.Create(
(([Version]$TargetObject.Properties.kubernetesVersion) -ge $minVersion),
$LocalizedData.AKSVersion,
$TargetObject.Properties.kubernetesVersion
);
}
elseif ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters/agentPools') {
$Assert.NullOrEmpty($TargetObject, 'Properties.orchestratorVersion').Result -or
@ -26,11 +29,14 @@ Rule 'Azure.AKS.Version' -Type 'Microsoft.ContainerService/managedClusters', 'Mi
# Synopsis: AKS agent pools should run the same Kubernetes version as the cluster
Rule 'Azure.AKS.PoolVersion' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
$clusterVersion = $TargetObject.Properties.kubernetesVersion
foreach ($pool in $TargetObject.Properties.agentPoolProfiles) {
$Assert.
HasDefaultValue($pool, 'orchestratorVersion', $clusterVersion).
Reason($LocalizedData.AKSNodePoolVersion, $pool.name, $pool.orchestratorVersion)
$clusterVersion = $TargetObject.Properties.kubernetesVersion;
$agentPools = @(GetAgentPoolProfiles);
if ($agentPools.Length -eq 0) {
return $Assert.Pass();
}
foreach ($agentPool in $agentPools) {
$Assert.HasDefaultValue($agentPool, 'orchestratorVersion', $clusterVersion).
Reason($LocalizedData.AKSNodePoolVersion, $agentPool.name, $agentPool.orchestratorVersion);
}
}
@ -46,37 +52,24 @@ Rule 'Azure.AKS.NetworkPolicy' -Type 'Microsoft.ContainerService/managedClusters
# Synopsis: AKS node pools should use scale sets
Rule 'Azure.AKS.PoolScaleSet' -Type 'Microsoft.ContainerService/managedClusters', 'Microsoft.ContainerService/managedClusters/agentPools' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
$result = $True
if ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters') {
$TargetObject.Properties.agentPoolProfiles | ForEach-Object {
if ($_.type -ne 'VirtualMachineScaleSets') {
$Assert.Fail($LocalizedData.AKSNodePoolType, $_.name)
}
}
$agentPools = @(GetAgentPoolProfiles);
if ($agentPools.Length -eq 0) {
return $Assert.Pass();
}
elseif ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters/agentPools') {
$result = $TargetObject.Properties.type -eq 'VirtualMachineScaleSets'
if (!$result) {
$Assert.Fail($LocalizedData.AKSNodePoolType, $TargetObject.name)
}
foreach ($agentPool in $agentPools) {
$Assert.HasFieldValue($agentPool, 'type', 'VirtualMachineScaleSets').
Reason($LocalizedData.AKSNodePoolType, $agentPool.name);
}
return $result
}
# Synopsis: AKS nodes should use a minimum number of pods
Rule 'Azure.AKS.NodeMinPods' -Type 'Microsoft.ContainerService/managedClusters', 'Microsoft.ContainerService/managedClusters/agentPools' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
$agentPools = $Null;
if ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters') {
$agentPools = @($TargetObject.Properties.agentPoolProfiles);
if ($agentPools.Length -eq 0) {
return $Assert.Pass();
}
}
elseif ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters/agentPools') {
$agentPools = @($TargetObject.Properties);
$agentPools = @(GetAgentPoolProfiles);
if ($agentPools.Length -eq 0) {
return $Assert.Pass();
}
foreach ($agentPool in $agentPools) {
$Assert.GreaterOrEqual($agentPool, 'maxPods', $Configuration.Azure_AKSNodeMinimumMaxPods)
$Assert.GreaterOrEqual($agentPool, 'maxPods', $Configuration.Azure_AKSNodeMinimumMaxPods);
}
} -Configure @{ Azure_AKSNodeMinimumMaxPods = 50 }
@ -85,23 +78,23 @@ Rule 'Azure.AKS.Name' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{
# https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftcontainerservice
# Between 1 and 63 characters long
$Assert.GreaterOrEqual($PSRule, 'TargetName', 1)
$Assert.LessOrEqual($PSRule, 'TargetName', 63)
$Assert.GreaterOrEqual($PSRule, 'TargetName', 1);
$Assert.LessOrEqual($PSRule, 'TargetName', 63);
# Alphanumerics, underscores, and hyphens
# Start and end with alphanumeric
$Assert.Match($PSRule, 'TargetName', '^[A-Za-z0-9](-|\w)*[A-Za-z0-9]$')
$Assert.Match($PSRule, 'TargetName', '^[A-Za-z0-9](-|\w)*[A-Za-z0-9]$');
}
# Synopsis: Use AKS naming requirements for DNS prefix
Rule 'Azure.AKS.DNSPrefix' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
# Between 1 and 54 characters long
$Assert.GreaterOrEqual($TargetObject, 'Properties.dnsPrefix', 1)
$Assert.LessOrEqual($TargetObject, 'Properties.dnsPrefix', 54)
$Assert.GreaterOrEqual($TargetObject, 'Properties.dnsPrefix', 1);
$Assert.LessOrEqual($TargetObject, 'Properties.dnsPrefix', 54);
# Alphanumerics and hyphens
# Start and end with alphanumeric
$Assert.Match($TargetObject, 'Properties.dnsPrefix', '^[A-Za-z0-9]((-|[A-Za-z0-9]){0,}[A-Za-z0-9]){0,}$')
$Assert.Match($TargetObject, 'Properties.dnsPrefix', '^[A-Za-z0-9]((-|[A-Za-z0-9]){0,}[A-Za-z0-9]){0,}$');
}
# Synopsis: Configure AKS clusters to use managed identities for managing cluster infrastructure.
@ -111,10 +104,41 @@ Rule 'Azure.AKS.ManagedIdentity' -Type 'Microsoft.ContainerService/managedCluste
# Synopsis: Use a Standard load-balancer with AKS clusters
Rule 'Azure.AKS.StandardLB' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
$Assert.HasFieldValue($TargetObject, 'Properties.networkProfile.loadBalancerSku', 'standard')
$Assert.HasFieldValue($TargetObject, 'Properties.networkProfile.loadBalancerSku', 'standard');
}
# Synopsis: AKS clusters should use Azure Policy add-on
Rule 'Azure.AKS.AzurePolicyAddOn' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{ release = 'GA'; ruleSet = '2020_12' } {
$Assert.HasFieldValue($TargetObject, 'Properties.addonProfiles.azurePolicy.enabled', $True)
$Assert.HasFieldValue($TargetObject, 'Properties.addonProfiles.azurePolicy.enabled', $True);
}
#region Helper functions
function global:GetAgentPoolProfiles {
[CmdletBinding()]
[OutputType([PSObject])]
param ()
process {
if ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters') {
$TargetObject.Properties.agentPoolProfiles;
@(GetSubResources -ResourceType 'Microsoft.ContainerService/managedClusters/agentPools' | ForEach-Object {
[PSCustomObject]@{
name = $_.name
type = $_.type
maxPods = $_.properties.maxPods
orchestratorVersion = $_.properties.orchestratorVersion
}
});
}
elseif ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters/agentPools') {
[PSCustomObject]@{
name = $TargetObject.name
type = $TargetObject.properties.type
maxPods = $TargetObject.properties.maxPods
orchestratorVersion = $TargetObject.properties.orchestratorVersion
}
}
}
}
#endregion Helper functions

Просмотреть файл

@ -103,7 +103,7 @@ function global:SupportsAcceleratedNetworking {
}
$vmSize = $TargetObject.Properties.hardwareProfile.vmSize;
if ($vmSize -notlike 'Standard_*_*') {
if ($vmSize -notLike 'Standard_*_*') {
if ($vmSize -match '^Standard_(F|B[1-2][0-9]ms)') {
return $True;
}
@ -122,13 +122,13 @@ function global:SupportsAcceleratedNetworking {
# Generation v2
if ($generation -eq 'v2') {
if ($size -notmatch '^(A|NC|DS1$|D1$|F[1-2]s)') {
if ($size -notMatch '^(A|NC|DS1$|D1$|F[1-2]s)') {
return $True;
}
}
# Generation v3
elseif ($generation -eq 'v3') {
if ($size -notmatch '^(E2s?|E[2-8]-2|D2s?|NC)') {
if ($size -notMatch '^(E2s?|E[2-8]-2|D2s?|NC)') {
return $True;
}
}
@ -164,7 +164,7 @@ function global:IsAppGwWAF {
if ($PSRule.TargetType -ne 'Microsoft.Network/applicationGateways') {
return $False;
}
if ($TargetObject.Properties.sku.tier -notin ('WAF', 'WAF_v2')) {
if ($TargetObject.Properties.sku.tier -notIn ('WAF', 'WAF_v2')) {
return $False;
}
if ($TargetObject.Properties.webApplicationFirewallConfiguration.enabled -ne $True) {
@ -179,7 +179,7 @@ function global:IsWindowsOS {
[OutputType([System.Boolean])]
param ()
process {
if ($PSRule.TargetType -notin 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets') {
if ($PSRule.TargetType -notIn 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets') {
return $False;
}
return ($TargetObject.Properties.storageProfile.osDisk.osType -eq 'Windows') -or
@ -194,7 +194,7 @@ function global:IsWindowsClientOS {
[OutputType([System.Boolean])]
param ()
process {
if ($PSRule.TargetType -notin 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets') {
if ($PSRule.TargetType -notIn 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets') {
return $False;
}
return $TargetObject.Properties.storageProfile.imageReference.publisher -eq 'MicrosoftWindowsDesktop';
@ -236,7 +236,7 @@ function global:SupportsTags {
process {
if (
($PSRule.TargetType -eq 'Microsoft.Subscription') -or
($PSRule.TargetType -notlike 'Microsoft.*/*') -or
($PSRule.TargetType -notLike 'Microsoft.*/*') -or
($PSRule.TargetType -like 'Microsoft.Addons/*') -or
($PSRule.TargetType -like 'Microsoft.Advisor/*') -or
($PSRule.TargetType -like 'Microsoft.Authorization/*') -or

Просмотреть файл

@ -63,8 +63,8 @@ Describe 'Azure.AKS' -Tag AKS {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -BeIn 'cluster-A', 'cluster-C', 'cluster-D';
$ruleResult.Length | Should -Be 4;
$ruleResult.TargetName | Should -BeIn 'cluster-A', 'cluster-C', 'cluster-D', 'system';
}
It 'Azure.AKS.PoolVersion' {
@ -127,8 +127,8 @@ Describe 'Azure.AKS' -Tag AKS {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -Be 'cluster-C', 'cluster-D';
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -BeIn 'cluster-C', 'cluster-D', 'system';
}
It 'Azure.AKS.NodeMinPods' {
@ -143,8 +143,8 @@ Describe 'Azure.AKS' -Tag AKS {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -Be 'cluster-C', 'cluster-D';
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -BeIn 'cluster-C', 'cluster-D', 'system';
}
It 'Azure.AKS.ManagedIdentity' {
@ -312,25 +312,29 @@ Describe 'Azure.AKS' -Tag AKS {
# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -Be 'clusterA/agentpool2';
$ruleResult | Should -BeNullOrEmpty;
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterA/agentpool3', 'clusterA/agentpool4';
$ruleResult.Length | Should -Be 4;
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterB', 'clusterC/agentpool3', 'clusterC/agentpool4';
}
It 'Azure.AKS.PoolVersion' {
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.AKS.PoolVersion' };
# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'clusterA';
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'clusterA';
$ruleResult.TargetName | Should -BeIn 'clusterB';
}
It 'Azure.AKS.PoolScaleSet' {
@ -340,13 +344,13 @@ Describe 'Azure.AKS' -Tag AKS {
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -Be 'clusterA/agentpool2';
$ruleResult.TargetName | Should -Be 'clusterA';
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterA/agentpool3', 'clusterA/agentpool4';
$ruleResult.TargetName | Should -BeIn 'clusterB', 'clusterC/agentpool3', 'clusterC/agentpool4';
}
It 'Azure.AKS.NodeMinPods' {
@ -356,13 +360,13 @@ Describe 'Azure.AKS' -Tag AKS {
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -Be 'clusterA/agentpool2';
$ruleResult.TargetName | Should -Be 'clusterA';
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterA/agentpool3', 'clusterA/agentpool4';
$ruleResult.TargetName | Should -BeIn 'clusterB', 'clusterC/agentpool3', 'clusterC/agentpool4';
}
It 'Azure.AKS.ManagedIdentity' {
@ -375,8 +379,8 @@ Describe 'Azure.AKS' -Tag AKS {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'clusterA';
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterB';
}
It 'Azure.AKS.StandardLB' {
@ -389,8 +393,8 @@ Describe 'Azure.AKS' -Tag AKS {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'clusterA';
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterB';
}
It 'Azure.AKS.AzurePolicyAddOn' {
@ -398,7 +402,9 @@ Describe 'Azure.AKS' -Tag AKS {
# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -BeNullOrEmpty;
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'clusterB';
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });

Просмотреть файл

@ -259,8 +259,12 @@ Describe 'Azure.Storage' -Tag Storage {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'storage1', 'storage1/default/arm';
$ruleResult.Length | Should -Be 1;
@($ruleResult[0].TargetObject.resources | Where-Object {
$_.type -eq 'Microsoft.Storage/storageAccounts/blobServices/containers' -and
$_.name -eq 'storage1/default/arm'
}).Length | Should -Be 1
# $ruleResult.TargetName | Should -BeIn 'storage1', 'storage1/default/arm';
}
}
}

Просмотреть файл

@ -40,6 +40,9 @@
<None Update="Resources.FrontDoor.Template.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources.KeyVault.Template.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources.Parameters.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

Просмотреть файл

@ -6,6 +6,14 @@
"defaultValue": "clusterA",
"type": "string"
},
"clusterName2": {
"defaultValue": "clusterB",
"type": "string"
},
"clusterName3": {
"defaultValue": "clusterC",
"type": "string"
},
"clusterLocation": {
"defaultValue": "[resourceGroup().location]",
"type": "string"
@ -100,12 +108,73 @@
"osType": "Linux"
}
},
{
"type": "Microsoft.ContainerService/managedClusters",
"apiVersion": "2019-10-01",
"name": "[parameters('clusterName2')]",
"location": "[parameters('clusterLocation')]",
"identity": {
"type": "SystemAssigned"
},
"properties": {
"kubernetesVersion": "1.19.7",
"dnsPrefix": "[concat('dns-', parameters('clusterName'))]",
"agentPoolProfiles": [
{
"name": "agentpool1",
"count": 3,
"vmSize": "Standard_B2ms",
"osDiskSizeGB": 100,
"vnetSubnetID": "[concat(parameters('vnetId'), '/subnets/subnet-01')]",
"maxPods": 50,
"type": "VirtualMachineScaleSets",
"osType": "Linux"
}
],
"servicePrincipalProfile": {
"clientId": "00000000-0000-0000-0000-000000000000"
},
"addonProfiles": {
"aciConnectorLinux": {
"enabled": true,
"config": {
"SubnetName": "subnet-aci"
}
},
"azurePolicy": {
"enabled": false
},
"httpApplicationRouting": {
"enabled": true
},
"kubeDashboard": {
"enabled": true
},
"omsagent": {
"enabled": true,
"config": {
"logAnalyticsWorkspaceResourceID": "[parameters('workspaceId')]"
}
}
},
"enableRBAC": true,
"enablePodSecurityPolicy": false,
"networkProfile": {
"networkPlugin": "azure",
"networkPolicy": "azure",
"loadBalancerSku": "Standard",
"serviceCidr": "192.168.0.0/16",
"dnsServiceIP": "192.168.0.4",
"dockerBridgeCidr": "172.17.0.1/16"
}
}
},
{
"type": "Microsoft.ContainerService/managedClusters/agentPools",
"apiVersion": "2019-10-01",
"name": "[concat(parameters('clusterName'), '/agentpool3')]",
"name": "[concat(parameters('clusterName3'), '/agentpool3')]",
"dependsOn": [
"[resourceId('Microsoft.ContainerService/managedClusters', parameters('clusterName'))]"
"[resourceId('Microsoft.ContainerService/managedClusters', parameters('clusterName3'))]"
],
"properties": {
"count": 3,
@ -121,9 +190,9 @@
{
"type": "Microsoft.ContainerService/managedClusters/agentPools",
"apiVersion": "2019-10-01",
"name": "[concat(parameters('clusterName'), '/agentpool4')]",
"name": "[concat(parameters('clusterName3'), '/agentpool4')]",
"dependsOn": [
"[resourceId('Microsoft.ContainerService/managedClusters', parameters('clusterName'))]"
"[resourceId('Microsoft.ContainerService/managedClusters', parameters('clusterName3'))]"
],
"properties": {
"count": 3,

Просмотреть файл

@ -203,10 +203,10 @@
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.ContainerService/managedClusters/cluster-D",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.ContainerService/managedClusters/cluster-D",
"Identity": {
"PrincipalId": "00000000-0000-0000-0000-000000000000",
"TenantId": "00000000-0000-0000-0000-000000000000",
"Type": "SystemAssigned",
"UserAssignedIdentities": null
"PrincipalId": "00000000-0000-0000-0000-000000000000",
"TenantId": "00000000-0000-0000-0000-000000000000",
"Type": "SystemAssigned",
"UserAssignedIdentities": null
},
"Kind": null,
"Location": "region",
@ -217,132 +217,170 @@
"ParentResource": null,
"Plan": null,
"Properties": {
"provisioningState": "Succeeded",
"kubernetesVersion": "1.19.7",
"dnsPrefix": "cluster-D",
"fqdn": "cluster-D-nnnnnnnn.hcp.region.azmk8s.io",
"agentPoolProfiles": [
{
"name": "agentpool",
"count": 1,
"vmSize": "Standard_B2ms",
"osDiskSizeGB": 100,
"vnetSubnetID": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/vnet-A/subnets/subnet-A",
"maxPods": 50,
"type": "VirtualMachineScaleSets",
"provisioningState": "Succeeded",
"orchestratorVersion": "1.19.7",
"nodeLabels": {},
"mode": "System",
"osType": "Linux",
"nodeImageVersion": "AKSUbuntu:1604:2020.05.13"
}
],
"windowsProfile": {
"adminUsername": "azureuser"
},
"servicePrincipalProfile": {
"clientId": "msi"
},
"addonProfiles": {
"aciConnectorLinux": {
"enabled": true,
"config": {
"SubnetName": "subnet-B"
},
"identity": {
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aciconnectorlinux-cluster-D",
"clientId": "00000000-0000-0000-0000-000000000000",
"objectId": "00000000-0000-0000-0000-000000000000"
}
},
"azurePolicy": {
"enabled": true,
"config": {
"version": "v2"
},
"identity": {
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/azurepolicy-cluster-D",
"clientId": "00000000-0000-0000-0000-000000000000",
"objectId": "00000000-0000-0000-0000-000000000000"
}
},
"httpApplicationRouting": {
"enabled": true,
"config": {
"HTTPApplicationRoutingZoneName": "dca47a62fed041939528.region.aksapp.io"
},
"identity": {
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/httpapplicationrouting-cluster-D",
"clientId": "00000000-0000-0000-0000-000000000000",
"objectId": "00000000-0000-0000-0000-000000000000"
}
},
"kubeDashboard": {
"enabled": false,
"config": null
},
"omsagent": {
"enabled": true,
"config": {
"logAnalyticsWorkspaceResourceID": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/defaultresourcegroup-eus/providers/microsoft.operationalinsights/workspaces/defaultworkspace-00000000-0000-0000-0000-000000000000-region"
},
"identity": {
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/omsagent-cluster-D",
"clientId": "00000000-0000-0000-0000-000000000000",
"objectId": "00000000-0000-0000-0000-000000000000"
}
}
},
"nodeResourceGroup": "MC_test-rg_cluster-D_region",
"enableRBAC": true,
"enablePodSecurityPolicy": true,
"networkProfile": {
"networkPlugin": "azure",
"networkPolicy": "azure",
"loadBalancerSku": "Standard",
"loadBalancerProfile": {
"managedOutboundIPs": {
"count": 1
},
"effectiveOutboundIPs": [
"provisioningState": "Succeeded",
"kubernetesVersion": "1.19.7",
"dnsPrefix": "cluster-D",
"fqdn": "cluster-D-nnnnnnnn.hcp.region.azmk8s.io",
"agentPoolProfiles": [
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MC_test-rg_cluster-D_region/providers/Microsoft.Network/publicIPAddresses/00000000-0000-0000-0000-000000000000"
"name": "agentpool",
"count": 1,
"vmSize": "Standard_B2ms",
"osDiskSizeGB": 100,
"vnetSubnetID": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/vnet-A/subnets/subnet-A",
"maxPods": 50,
"type": "VirtualMachineScaleSets",
"provisioningState": "Succeeded",
"orchestratorVersion": "1.19.7",
"nodeLabels": {},
"mode": "System",
"osType": "Linux",
"nodeImageVersion": "AKSUbuntu:1604:2020.05.13"
}
]
],
"windowsProfile": {
"adminUsername": "azureuser"
},
"serviceCidr": "192.168.0.0/16",
"dnsServiceIP": "192.168.0.4",
"dockerBridgeCidr": "172.17.0.1/16",
"outboundType": "loadBalancer"
},
"aadProfile": {
"managed": true,
"adminGroupObjectIDs": null,
"tenantID": "00000000-0000-0000-0000-000000000000"
},
"maxAgentPools": 10,
"identityProfile": {
"kubeletidentity": {
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cluster-D-agentpool",
"clientId": "00000000-0000-0000-0000-000000000000",
"objectId": "00000000-0000-0000-0000-000000000000"
"servicePrincipalProfile": {
"clientId": "msi"
},
"addonProfiles": {
"aciConnectorLinux": {
"enabled": true,
"config": {
"SubnetName": "subnet-B"
},
"identity": {
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aciconnectorlinux-cluster-D",
"clientId": "00000000-0000-0000-0000-000000000000",
"objectId": "00000000-0000-0000-0000-000000000000"
}
},
"azurePolicy": {
"enabled": true,
"config": {
"version": "v2"
},
"identity": {
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/azurepolicy-cluster-D",
"clientId": "00000000-0000-0000-0000-000000000000",
"objectId": "00000000-0000-0000-0000-000000000000"
}
},
"httpApplicationRouting": {
"enabled": true,
"config": {
"HTTPApplicationRoutingZoneName": "dca47a62fed041939528.region.aksapp.io"
},
"identity": {
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/httpapplicationrouting-cluster-D",
"clientId": "00000000-0000-0000-0000-000000000000",
"objectId": "00000000-0000-0000-0000-000000000000"
}
},
"kubeDashboard": {
"enabled": false,
"config": null
},
"omsagent": {
"enabled": true,
"config": {
"logAnalyticsWorkspaceResourceID": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/defaultresourcegroup-eus/providers/microsoft.operationalinsights/workspaces/defaultworkspace-00000000-0000-0000-0000-000000000000-region"
},
"identity": {
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/omsagent-cluster-D",
"clientId": "00000000-0000-0000-0000-000000000000",
"objectId": "00000000-0000-0000-0000-000000000000"
}
}
},
"nodeResourceGroup": "MC_test-rg_cluster-D_region",
"enableRBAC": true,
"enablePodSecurityPolicy": true,
"networkProfile": {
"networkPlugin": "azure",
"networkPolicy": "azure",
"loadBalancerSku": "Standard",
"loadBalancerProfile": {
"managedOutboundIPs": {
"count": 1
},
"effectiveOutboundIPs": [
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MC_test-rg_cluster-D_region/providers/Microsoft.Network/publicIPAddresses/00000000-0000-0000-0000-000000000000"
}
]
},
"serviceCidr": "192.168.0.0/16",
"dnsServiceIP": "192.168.0.4",
"dockerBridgeCidr": "172.17.0.1/16",
"outboundType": "loadBalancer"
},
"aadProfile": {
"managed": true,
"adminGroupObjectIDs": null,
"tenantID": "00000000-0000-0000-0000-000000000000"
},
"maxAgentPools": 10,
"identityProfile": {
"kubeletidentity": {
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cluster-D-agentpool",
"clientId": "00000000-0000-0000-0000-000000000000",
"objectId": "00000000-0000-0000-0000-000000000000"
}
}
}
},
"ResourceGroupName": "test-rg",
"Type": "Microsoft.ContainerService/managedClusters",
"ResourceType": "Microsoft.ContainerService/managedClusters",
"ExtensionResourceType": null,
"Sku": {
"Name": "Basic",
"Tier": "Free",
"Size": null,
"Family": null,
"Model": null,
"Capacity": null
"Name": "Basic",
"Tier": "Free",
"Size": null,
"Family": null,
"Model": null,
"Capacity": null
},
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000"
}
},
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.ContainerService/managedClusters/cluster-E/agentPools/system",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.ContainerService/managedClusters/cluster-E/agentPools/system",
"Identity": null,
"Kind": null,
"Location": null,
"ManagedBy": null,
"ResourceName": "system",
"Name": "system",
"ExtensionResourceName": null,
"ParentResource": "managedClusters/cluster-E",
"Properties": {
"count": 2,
"vmSize": "Standard_D2s_v3",
"osDiskSizeGB": 32,
"osDiskType": "Ephemeral",
"vnetSubnetID": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/vnet-A/subnets/subnet-A",
"maxPods": 50,
"type": "VirtualMachineScaleSets",
"maxCount": 2,
"minCount": 2,
"enableAutoScaling": true,
"provisioningState": "Succeeded",
"powerState": {
"code": "Running"
},
"orchestratorVersion": "1.19.7",
"nodeLabels": {},
"mode": "System",
"osType": "Linux",
"nodeImageVersion": "AKSUbuntu-1804containerd-2021.01.28"
},
"ResourceGroupName": "test-rg",
"Type": "Microsoft.ContainerService/managedClusters/agentPools",
"ResourceType": "Microsoft.ContainerService/managedClusters/agentPools",
"Sku": null,
"Tags": null
}
]

Просмотреть файл

@ -8,6 +8,7 @@
"parameters": {
"vaultName": {
"type": "string",
"defaultValue": "keyvault1",
"metadata": {
"description": "Required. The name of the Key Vault."
}
@ -91,7 +92,7 @@
},
"workspaceId": {
"type": "string",
"defaultValue": "",
"defaultValue": "/subscriptions/workspaceId",
"metadata": {
"description": "Optional. The workspace to store audit logs."
}
@ -129,30 +130,48 @@
"enableSoftDelete": "[parameters('useSoftDelete')]",
"enablePurgeProtection": "[parameters('usePurgeProtection')]"
},
"tags": "[parameters('tags')]",
"resources": [
{
"comments": "Enable monitoring of Key Vault operations.",
"condition": "[not(empty(parameters('workspaceId')))]",
"type": "Microsoft.KeyVault/vaults/providers/diagnosticSettings",
"name": "[concat(parameters('vaultName'), '/Microsoft.Insights/service')]",
"apiVersion": "2016-09-01",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('vaultName'))]"
],
"properties": {
"workspaceId": "[parameters('workspaceId')]",
"logs": [
{
"category": "AuditEvent",
"enabled": true
}
]
"tags": "[parameters('tags')]"
},
{
"comments": "Enable monitoring of Key Vault operations.",
"condition": "[not(empty(parameters('workspaceId')))]",
"type": "Microsoft.KeyVault/vaults/providers/diagnosticSettings",
"name": "[concat(parameters('vaultName'), '/Microsoft.Insights/service')]",
"apiVersion": "2016-09-01",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('vaultName'))]"
],
"properties": {
"workspaceId": "[parameters('workspaceId')]",
"logs": [
{
"category": "AuditEvent",
"enabled": true
}
}
]
]
}
},
{
"comments": "Enable monitoring of Key Vault operations.",
"condition": "[not(empty(parameters('workspaceId')))]",
"scope": "Microsoft.KeyVault/vaults/keyvault1",
"type": "Microsoft.Insights/diagnosticsettings",
"name": "monitor",
"apiVersion": "2016-09-01",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('vaultName'))]"
],
"properties": {
"workspaceId": "[parameters('workspaceId')]",
"logs": [
{
"category": "AuditEvent",
"enabled": true
}
]
}
}
],
"outputs": {

Просмотреть файл

@ -6,6 +6,7 @@ using Newtonsoft.Json.Linq;
using PSRule.Rules.Azure.Data.Template;
using System;
using System.IO;
using System.Linq;
using Xunit;
using static PSRule.Rules.Azure.Data.Template.TemplateVisitor;
@ -119,6 +120,44 @@ namespace PSRule.Rules.Azure
Assert.Equal("my-nic", resources[3]["name"].Value<string>());
}
[Fact]
public void NestedResources()
{
var resources = ProcessTemplate(GetSourcePath("Resources.KeyVault.Template.json"), null);
Assert.NotNull(resources);
Assert.Single(resources);
var subResources = resources[0]["resources"].Value<JArray>();
Assert.Equal(2, subResources.Count);
Assert.Equal("keyvault1/Microsoft.Insights/service", subResources[0]["name"].Value<string>());
Assert.Equal("monitor", subResources[1]["name"].Value<string>());
}
[Fact]
public void TryParentResourceId()
{
const string expected1 = "Microsoft.ServiceBus/namespaces/besubns";
const string expected2 = "Microsoft.KeyVault/vaults/keyvault1";
var actual = JObject.Parse("{ \"type\": \"Microsoft.ServiceBus/namespaces/topics\", \"name\": \"besubns/demo1\" }");
TemplateContext.TryParentResourceId(actual, out string[] resourceId);
Assert.Equal(expected1, resourceId[0]);
actual = JObject.Parse("{ \"type\": \"Microsoft.KeyVault/vaults\", \"name\": \"keyvault1\" }");
TemplateContext.TryParentResourceId(actual, out resourceId);
Assert.Empty(resourceId);
actual = JObject.Parse("{ \"type\": \"Microsoft.KeyVault/vaults/providers/diagnosticsettings\", \"name\": \"keyvault1/Microsoft.Insights/service\" }");
TemplateContext.TryParentResourceId(actual, out resourceId);
Assert.Equal(expected2, resourceId[0]);
actual = JObject.Parse("{ \"type\": \"Microsoft.Insights/diagnosticsettings\", \"name\": \"auditing-storage\", \"scope\": \"Microsoft.KeyVault/vaults/keyvault1\" }");
TemplateContext.TryParentResourceId(actual, out resourceId);
Assert.Equal(expected2, resourceId[0]);
}
#region Helper methods
private static string GetSourcePath(string fileName)
{
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
@ -132,7 +171,7 @@ namespace PSRule.Rules.Azure
var context = new TemplateContext();
context.Load(parametersObject);
visitor.Visit(context, "deployment", templateObject);
return context.Resources.ToArray();
return context.GetResources().Select(i => i.Value).ToArray();
}
private static T ReadFile<T>(string path)
@ -142,5 +181,7 @@ namespace PSRule.Rules.Azure
return JsonConvert.DeserializeObject<T>(File.ReadAllText(path));
}
#endregion Helper methods
}
}