Родитель
e6fa61641b
Коммит
871b94b671
|
@ -4,7 +4,7 @@
|
|||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "test",
|
||||
"label": "Test",
|
||||
"detail": "Build and run unit tests.",
|
||||
"type": "shell",
|
||||
"command": "Invoke-Build Test -AssertStyle Client",
|
||||
|
@ -31,7 +31,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"label": "build",
|
||||
"label": "Build",
|
||||
"detail": "Build module.",
|
||||
"type": "shell",
|
||||
"command": "Invoke-Build Build",
|
||||
|
@ -86,6 +86,13 @@
|
|||
"type": "shell",
|
||||
"command": "Invoke-Build BuildBaselineDocs",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Export data",
|
||||
"detail": "Export a list of resource providers from an Azure subscription.",
|
||||
"type": "shell",
|
||||
"command": "Invoke-Build ExportData",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
- General improvements:
|
||||
- Added support for `providers` template function. [#177](https://github.com/Microsoft/PSRule.Rules.Azure/issues/177)
|
||||
- Added support for `dateTimeAdd` template function. [#516](https://github.com/Microsoft/PSRule.Rules.Azure/issues/516)
|
||||
|
||||
## v0.16.0
|
||||
|
||||
What's changed since v0.15.0:
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"AzureCloud": {
|
||||
"name": "AzureCloud",
|
||||
"gallery": "https://gallery.azure.com/",
|
||||
"graph": "https://graph.windows.net/",
|
||||
"portal": "https://portal.azure.com",
|
||||
"graphAudience": "https://graph.windows.net/",
|
||||
"activeDirectoryDataLake": "https://datalake.azure.net/",
|
||||
"batch": "https://batch.core.windows.net/",
|
||||
"media": "https://rest.media.azure.net",
|
||||
"sqlManagement": "https://management.core.windows.net:8443/",
|
||||
"vmImageAliasDoc": "https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/arm-compute/quickstart-templates/aliases.json",
|
||||
"resourceManager": "https://management.azure.com/",
|
||||
"authentication": {
|
||||
"loginEndpoint": "https://login.microsoftonline.com/",
|
||||
"audiences": [
|
||||
"https://management.core.windows.net/",
|
||||
"https://management.azure.com/"
|
||||
],
|
||||
"tenant": "common",
|
||||
"identityProvider": "AAD"
|
||||
},
|
||||
"suffixes": {
|
||||
"acrLoginServer": ".azurecr.io",
|
||||
"azureDatalakeAnalyticsCatalogAndJob": "azuredatalakeanalytics.net",
|
||||
"azureDatalakeStoreFileSystem": "azuredatalakestore.net",
|
||||
"azureFrontDoorEndpointSuffix": "azurefd.net",
|
||||
"keyvaultDns": ".vault.azure.net",
|
||||
"sqlServerHostname": ".database.windows.net",
|
||||
"storage": "core.windows.net"
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -35,7 +35,7 @@ Currently the following limitations also apply:
|
|||
|
||||
- Nested templates are expanded, external templates are not.
|
||||
- Deployment resources that link to an external template are returned as a resource.
|
||||
- The `providers` template function is not supported.
|
||||
- The `environment`, and `pickZones` template functions are not supported.
|
||||
- References to Key Vault secrets are not expanded. A placeholder value is used instead.
|
||||
- Multi-line strings and user-defined functions are not supported.
|
||||
|
||||
|
|
|
@ -212,7 +212,25 @@ task TestDotNet {
|
|||
}
|
||||
}
|
||||
|
||||
task CopyModule {
|
||||
task MinifyData {
|
||||
$dataPath = Join-Path -Path $PWD -ChildPath 'data';
|
||||
$dataOutPath = Join-Path -Path $PWD -ChildPath 'out/data';
|
||||
if (!(Test-Path -Path $dataOutPath)) {
|
||||
$Null = New-Item -Path $dataOutPath -ItemType Directory -Force;
|
||||
}
|
||||
|
||||
# Providers
|
||||
$filePath = Join-Path -Path $dataPath -ChildPath 'providers.json';
|
||||
$fileOutPath = Join-Path -Path $dataOutPath -ChildPath 'providers.json';
|
||||
Get-Content -Path $filePath -Raw | ConvertFrom-Json | ConvertTo-Json -Depth 100 -Compress | Set-Content -Path $fileOutPath;
|
||||
|
||||
# Environments
|
||||
$filePath = Join-Path -Path $dataPath -ChildPath 'environments.json';
|
||||
$fileOutPath = Join-Path -Path $dataOutPath -ChildPath 'environments.json';
|
||||
Get-Content -Path $filePath -Raw | ConvertFrom-Json | ConvertTo-Json -Depth 100 -Compress | Set-Content -Path $fileOutPath;
|
||||
}
|
||||
|
||||
task CopyModule MinifyData, {
|
||||
CopyModuleFiles -Path src/PSRule.Rules.Azure -DestinationPath out/modules/PSRule.Rules.Azure;
|
||||
|
||||
# Copy LICENSE
|
||||
|
@ -220,6 +238,9 @@ task CopyModule {
|
|||
|
||||
# Copy third party notices
|
||||
Copy-Item -Path ThirdPartyNotices.txt -Destination out/modules/PSRule.Rules.Azure;
|
||||
|
||||
# Copy data
|
||||
Copy-Item -Path 'out/data/*.json' -Destination out/modules/PSRule.Rules.Azure -Recurse;
|
||||
}
|
||||
|
||||
# Synopsis: Build modules only
|
||||
|
@ -249,6 +270,26 @@ task TestModule ModuleDependencies, Pester, PSScriptAnalyzer, {
|
|||
}
|
||||
}
|
||||
|
||||
task IntegrationTest ModuleDependencies, Pester, PSScriptAnalyzer, {
|
||||
# Run Pester tests
|
||||
$pesterParams = @{ Path = (Join-Path -Path $PWD -ChildPath tests/Integration); OutputFile = 'reports/pester-unit.xml'; OutputFormat = 'NUnitXml'; PesterOption = @{ IncludeVSCodeMarker = $True }; PassThru = $True; };
|
||||
|
||||
if (!(Test-Path -Path reports)) {
|
||||
$Null = New-Item -Path reports -ItemType Directory -Force;
|
||||
}
|
||||
|
||||
$results = Invoke-Pester @pesterParams;
|
||||
|
||||
# Throw an error if pester tests failed
|
||||
if ($Null -eq $results) {
|
||||
throw 'Failed to get Pester test results.';
|
||||
}
|
||||
elseif ($results.FailedCount -gt 0) {
|
||||
throw "$($results.FailedCount) tests failed.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Synopsis: Run validation
|
||||
task Rules PSRule, {
|
||||
$assertParams = @{
|
||||
|
@ -327,6 +368,38 @@ task ScaffoldHelp Build, BuildRuleDocs, {
|
|||
Update-MarkdownHelp -Path '.\docs\commands\PSRule.Rules.Azure\en-US';
|
||||
}
|
||||
|
||||
task ExportProviders {
|
||||
$providers = Get-AzResourceProvider -ListAvailable;
|
||||
$index = @{}
|
||||
foreach ($provider in $providers) {
|
||||
$namespace = $provider.ProviderNamespace;
|
||||
$provider.PSObject.Properties.Remove('RegistrationState');
|
||||
$provider.PSObject.Properties.Remove('ProviderNamespace');
|
||||
|
||||
$resourceTypes = @{};
|
||||
$provider.ResourceTypes | ForEach-Object {
|
||||
$info = @{
|
||||
resourceType = $_.ResourceTypeName
|
||||
apiVersions = $_.ApiVersions
|
||||
locations = $_.Locations
|
||||
}
|
||||
$resourceTypes.Add($info.resourceType, $info);
|
||||
};
|
||||
$entry = @{
|
||||
types = $resourceTypes
|
||||
}
|
||||
$index.Add($namespace, $entry);
|
||||
}
|
||||
|
||||
$dataPath = Join-Path -Path $PWD -ChildPath 'data';
|
||||
if (!(Test-Path -Path $dataPath)) {
|
||||
$Null = New-Item -Path $dataPath -ItemType Directory -Force;
|
||||
}
|
||||
$index | ConvertTo-Json -Depth 20 | Set-Content ./data/providers.json;
|
||||
}
|
||||
|
||||
task ExportData ExportProviders
|
||||
|
||||
# Synopsis: Add shipit build tag
|
||||
task TagBuild {
|
||||
if ($Null -ne $Env:BUILD_DEFINITIONNAME) {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using PSRule.Rules.Azure.Data.Template;
|
||||
using PSRule.Rules.Azure.Pipeline;
|
||||
using PSRule.Rules.Azure.Resources;
|
||||
using System;
|
||||
|
@ -221,4 +222,116 @@ namespace PSRule.Rules.Azure
|
|||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ResourceProviderConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(ResourceProvider) || objectType == typeof(Dictionary<string, ResourceProvider>);
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (objectType == typeof(ResourceProvider))
|
||||
{
|
||||
var resultObject = existingValue as ResourceProvider ?? new ResourceProvider();
|
||||
ReadObject(resultObject, reader, serializer);
|
||||
return resultObject;
|
||||
}
|
||||
|
||||
var resultDictionary = existingValue as Dictionary<string, ResourceProvider> ?? new Dictionary<string, ResourceProvider>(StringComparer.OrdinalIgnoreCase);
|
||||
ReadDictionary(resultDictionary, reader, serializer);
|
||||
return resultDictionary;
|
||||
}
|
||||
|
||||
private static void ReadObject(ResourceProvider value, JsonReader reader, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed);
|
||||
|
||||
reader.Read();
|
||||
string name = null;
|
||||
|
||||
// Read each token
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.PropertyName:
|
||||
name = reader.Value.ToString();
|
||||
break;
|
||||
|
||||
case JsonToken.StartObject:
|
||||
if (name == "types")
|
||||
{
|
||||
ReadType(value, reader, serializer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadType(ResourceProvider value, JsonReader reader, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed);
|
||||
|
||||
reader.Read();
|
||||
string name = null;
|
||||
|
||||
// Read each token
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.PropertyName:
|
||||
name = reader.Value.ToString();
|
||||
break;
|
||||
|
||||
case JsonToken.StartObject:
|
||||
var resourceType = serializer.Deserialize<ResourceProviderType>(reader);
|
||||
value.Types.Add(name, resourceType);
|
||||
break;
|
||||
}
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadDictionary(Dictionary<string, ResourceProvider> value, JsonReader reader, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed);
|
||||
|
||||
reader.Read();
|
||||
string name = null;
|
||||
|
||||
// Read each token
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.PropertyName:
|
||||
name = reader.Value.ToString();
|
||||
break;
|
||||
|
||||
case JsonToken.StartObject:
|
||||
var provider = new ResourceProvider();
|
||||
ReadObject(provider, reader, serializer);
|
||||
value.Add(name, provider);
|
||||
break;
|
||||
}
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ using System.Security.Cryptography;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using static PSRule.Rules.Azure.Data.Template.TemplateVisitor;
|
||||
|
||||
namespace PSRule.Rules.Azure.Data.Template
|
||||
|
@ -27,14 +28,12 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
{
|
||||
// Array and object
|
||||
new FunctionDescriptor("array", Array),
|
||||
new FunctionDescriptor("coalesce", Coalesce),
|
||||
new FunctionDescriptor("concat", Concat),
|
||||
new FunctionDescriptor("contains", Contains),
|
||||
new FunctionDescriptor("createArray", CreateArray),
|
||||
new FunctionDescriptor("empty", Empty),
|
||||
new FunctionDescriptor("first", First),
|
||||
new FunctionDescriptor("intersection", Intersection),
|
||||
new FunctionDescriptor("json", Json),
|
||||
new FunctionDescriptor("last", Last),
|
||||
new FunctionDescriptor("length", Length),
|
||||
new FunctionDescriptor("min", Min),
|
||||
|
@ -45,14 +44,20 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
new FunctionDescriptor("union", Union),
|
||||
|
||||
// Comparison
|
||||
new FunctionDescriptor("coalesce", Coalesce),
|
||||
new FunctionDescriptor("equals", Equals),
|
||||
new FunctionDescriptor("greater", Greater),
|
||||
new FunctionDescriptor("greaterOrEquals", GreaterOrEquals),
|
||||
new FunctionDescriptor("less", Less),
|
||||
new FunctionDescriptor("lessOrEquals", LessOrEquals),
|
||||
|
||||
// Date
|
||||
new FunctionDescriptor("dateTimeAdd", DateTimeAdd),
|
||||
new FunctionDescriptor("utcNow", UtcNow),
|
||||
|
||||
// Deployment
|
||||
new FunctionDescriptor("deployment", Deployment),
|
||||
// environment
|
||||
new FunctionDescriptor("parameters", Parameters),
|
||||
new FunctionDescriptor("variables", Variables),
|
||||
|
||||
|
@ -75,10 +80,14 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
new FunctionDescriptor("mul", Mul),
|
||||
new FunctionDescriptor("sub", Sub),
|
||||
|
||||
// Object
|
||||
new FunctionDescriptor("json", Json),
|
||||
|
||||
// Resource
|
||||
new FunctionDescriptor("extensionResourceId", ExtensionResourceId),
|
||||
new FunctionDescriptor("list", List), // Includes listAccountSas, listKeys, listSecrets, list*
|
||||
// providers
|
||||
// pickZones
|
||||
new FunctionDescriptor("providers", Providers),
|
||||
new FunctionDescriptor("reference", Reference),
|
||||
new FunctionDescriptor("resourceGroup", ResourceGroup),
|
||||
new FunctionDescriptor("resourceId", ResourceId),
|
||||
|
@ -119,7 +128,6 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
new FunctionDescriptor("uri", Uri),
|
||||
new FunctionDescriptor("uriComponent", UriComponent),
|
||||
new FunctionDescriptor("uriComponentToString", UriComponentToString),
|
||||
new FunctionDescriptor("utcNow", UtcNow),
|
||||
};
|
||||
|
||||
private static readonly CultureInfo AzureCulture = new CultureInfo("en-US");
|
||||
|
@ -395,15 +403,15 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
if (CountArgs(args) != 2)
|
||||
throw ArgumentsOutOfRange(nameof(Range), args);
|
||||
|
||||
if (!ExpressionHelpers.TryInt(args[0], out int startingInteger))
|
||||
throw new ArgumentException();
|
||||
if (!ExpressionHelpers.TryInt(args[0], out int startIndex))
|
||||
throw ArgumentInvalidInteger(nameof(Range), nameof(startIndex));
|
||||
|
||||
if (!ExpressionHelpers.TryInt(args[1], out int numberofElements))
|
||||
throw new ArgumentException();
|
||||
if (!ExpressionHelpers.TryInt(args[1], out int count))
|
||||
throw ArgumentInvalidInteger(nameof(Range), nameof(count));
|
||||
|
||||
var result = new int[numberofElements];
|
||||
for (var i = 0; i < numberofElements; i++)
|
||||
result[i] = startingInteger++;
|
||||
var result = new int[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
result[i] = startIndex++;
|
||||
|
||||
return new JArray(result);
|
||||
}
|
||||
|
@ -414,7 +422,7 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
throw ArgumentsOutOfRange(nameof(Skip), args);
|
||||
|
||||
if (!ExpressionHelpers.TryInt(args[1], out int numberToSkip))
|
||||
throw new ArgumentException();
|
||||
throw ArgumentInvalidInteger(nameof(Skip), nameof(numberToSkip));
|
||||
|
||||
int skip = numberToSkip <= 0 ? 0 : numberToSkip;
|
||||
if (ExpressionHelpers.TryString(args[0], out string soriginalValue))
|
||||
|
@ -435,7 +443,6 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
|
@ -633,6 +640,29 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
return new MockList(resourceId);
|
||||
}
|
||||
|
||||
internal static object Providers(TemplateContext context, object[] args)
|
||||
{
|
||||
var argCount = CountArgs(args);
|
||||
if (argCount < 1 || argCount > 2)
|
||||
throw ArgumentsOutOfRange(nameof(Providers), args);
|
||||
|
||||
if (!ExpressionHelpers.TryString(args[0], out string providerNamespace))
|
||||
throw ArgumentFormatInvalid(nameof(Providers));
|
||||
|
||||
string resourceType = null;
|
||||
if (argCount > 1 && !ExpressionHelpers.TryString(args[1], out resourceType))
|
||||
throw ArgumentFormatInvalid(nameof(Providers));
|
||||
|
||||
var resourceTypes = context.GetResourceType(providerNamespace, resourceType);
|
||||
if (resourceType == null)
|
||||
return resourceTypes;
|
||||
|
||||
if (resourceTypes == null || resourceTypes.Length == 0)
|
||||
throw ArgumentInvalidResourceType(nameof(Providers), providerNamespace, resourceType);
|
||||
|
||||
return resourceTypes[0];
|
||||
}
|
||||
|
||||
internal static object Reference(TemplateContext context, object[] args)
|
||||
{
|
||||
var argCount = CountArgs(args);
|
||||
|
@ -1004,6 +1034,45 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
|
||||
#endregion Comparison
|
||||
|
||||
#region Date
|
||||
|
||||
internal static object DateTimeAdd(TemplateContext context, object[] args)
|
||||
{
|
||||
var argCount = CountArgs(args);
|
||||
if (argCount < 2 || argCount > 3)
|
||||
throw ArgumentsOutOfRange(nameof(DateTimeAdd), args);
|
||||
|
||||
if (!ExpressionHelpers.TryString(args[0], out string start))
|
||||
throw ArgumentInvalidString(nameof(DateTimeAdd), "base");
|
||||
|
||||
if (!ExpressionHelpers.TryString(args[1], out string duration))
|
||||
throw ArgumentInvalidString(nameof(DateTimeAdd), nameof(duration));
|
||||
|
||||
string format = null;
|
||||
if (argCount == 3 && !ExpressionHelpers.TryString(args[2], out format))
|
||||
throw ArgumentInvalidString(nameof(DateTimeAdd), nameof(format));
|
||||
|
||||
var startTime = DateTime.Parse(start, AzureCulture);
|
||||
var timeToAdd = XmlConvert.ToTimeSpan(duration);
|
||||
var result = startTime.Add(timeToAdd);
|
||||
return format == null ? result.ToString(AzureCulture) : result.ToString(format, AzureCulture);
|
||||
}
|
||||
|
||||
internal static object UtcNow(TemplateContext context, object[] args)
|
||||
{
|
||||
var argCount = CountArgs(args);
|
||||
if (CountArgs(args) > 1)
|
||||
throw ArgumentsOutOfRange(nameof(UtcNow), args);
|
||||
|
||||
var format = "yyyyMMddTHHmmssZ";
|
||||
if (argCount == 1 && !ExpressionHelpers.TryString(args[0], out format))
|
||||
throw ArgumentInvalidString(nameof(UtcNow), nameof(format));
|
||||
|
||||
return DateTime.UtcNow.ToString(format, AzureCulture);
|
||||
}
|
||||
|
||||
#endregion Date
|
||||
|
||||
#region Logical
|
||||
|
||||
/// <summary>
|
||||
|
@ -1089,10 +1158,10 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
if (CountArgs(args) != 1)
|
||||
throw ArgumentsOutOfRange(nameof(Base64), args);
|
||||
|
||||
if (!ExpressionHelpers.TryString(args[0], out string value))
|
||||
throw new ArgumentException();
|
||||
if (!ExpressionHelpers.TryString(args[0], out string inputString))
|
||||
throw ArgumentInvalidString(nameof(Base64), nameof(inputString));
|
||||
|
||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
|
||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(inputString));
|
||||
}
|
||||
|
||||
internal static object Base64ToJson(TemplateContext context, object[] args)
|
||||
|
@ -1100,10 +1169,10 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
if (CountArgs(args) != 1)
|
||||
throw ArgumentsOutOfRange(nameof(Base64ToJson), args);
|
||||
|
||||
if (!ExpressionHelpers.TryString(args[0], out string value))
|
||||
throw new ArgumentException();
|
||||
if (!ExpressionHelpers.TryString(args[0], out string base64Value))
|
||||
throw ArgumentInvalidString(nameof(Base64ToJson), nameof(base64Value));
|
||||
|
||||
return JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(value)));
|
||||
return JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(base64Value)));
|
||||
}
|
||||
|
||||
internal static object Base64ToString(TemplateContext context, object[] args)
|
||||
|
@ -1111,10 +1180,10 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
if (CountArgs(args) != 1)
|
||||
throw ArgumentsOutOfRange(nameof(Base64ToString), args);
|
||||
|
||||
if (!ExpressionHelpers.TryString(args[0], out string value))
|
||||
throw new ArgumentException();
|
||||
if (!ExpressionHelpers.TryString(args[0], out string base64Value))
|
||||
throw ArgumentInvalidString(nameof(Base64ToString), nameof(base64Value));
|
||||
|
||||
return Encoding.UTF8.GetString(Convert.FromBase64String(value));
|
||||
return Encoding.UTF8.GetString(Convert.FromBase64String(base64Value));
|
||||
}
|
||||
|
||||
internal static object DataUri(TemplateContext context, object[] args)
|
||||
|
@ -1326,10 +1395,10 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
throw ArgumentsOutOfRange(nameof(Uri), args);
|
||||
|
||||
if (!ExpressionHelpers.TryString(args[0], out string baseUri))
|
||||
throw new ArgumentException(PSRuleResources.FunctionInvalidString, "baseUri");
|
||||
throw ArgumentInvalidString(nameof(Uri), "baseUri");
|
||||
|
||||
if (!ExpressionHelpers.TryString(args[1], out string relativeUri))
|
||||
throw new ArgumentException(PSRuleResources.FunctionInvalidString, "relativeUri");
|
||||
throw ArgumentInvalidString(nameof(Uri), "relativeUri");
|
||||
|
||||
var result = new Uri(new Uri(baseUri), relativeUri);
|
||||
return result.ToString();
|
||||
|
@ -1341,7 +1410,7 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
throw ArgumentsOutOfRange(nameof(UriComponent), args);
|
||||
|
||||
if (!ExpressionHelpers.TryString(args[0], out string stringToEncode))
|
||||
throw new ArgumentException(PSRuleResources.FunctionInvalidString, "stringToEncode");
|
||||
throw ArgumentInvalidString(nameof(UriComponent), "stringToEncode");
|
||||
|
||||
return HttpUtility.UrlEncode(stringToEncode);
|
||||
}
|
||||
|
@ -1352,24 +1421,11 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
throw ArgumentsOutOfRange(nameof(UriComponentToString), args);
|
||||
|
||||
if (!ExpressionHelpers.TryString(args[0], out string uriEncodedString))
|
||||
throw new ArgumentException(PSRuleResources.FunctionInvalidString, "uriEncodedString");
|
||||
throw ArgumentInvalidString(nameof(UriComponentToString), "uriEncodedString");
|
||||
|
||||
return HttpUtility.UrlDecode(uriEncodedString);
|
||||
}
|
||||
|
||||
internal static object UtcNow(TemplateContext context, object[] args)
|
||||
{
|
||||
var argCount = CountArgs(args);
|
||||
if (CountArgs(args) > 1)
|
||||
throw ArgumentsOutOfRange(nameof(UtcNow), args);
|
||||
|
||||
var format = "yyyyMMddTHHmmssZ";
|
||||
if (argCount == 1 && !ExpressionHelpers.TryString(args[0], out format))
|
||||
throw new ArgumentException(PSRuleResources.FunctionInvalidString, "format");
|
||||
|
||||
return DateTime.UtcNow.ToString(format, AzureCulture);
|
||||
}
|
||||
|
||||
internal static object Replace(TemplateContext context, object[] args)
|
||||
{
|
||||
if (CountArgs(args) != 3)
|
||||
|
@ -1387,7 +1443,6 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
string[] delimiter = null;
|
||||
|
||||
if (ExpressionHelpers.TryString(args[1], out string single))
|
||||
{
|
||||
delimiter = new string[] { single };
|
||||
|
@ -1556,6 +1611,22 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
);
|
||||
}
|
||||
|
||||
private static ExpressionArgumentException ArgumentInvalidString(string expression, string operand)
|
||||
{
|
||||
return new ExpressionArgumentException(
|
||||
expression,
|
||||
string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentInvalidString, operand, expression)
|
||||
);
|
||||
}
|
||||
|
||||
private static ExpressionArgumentException ArgumentInvalidResourceType(string expression, string providerNamespace, string resourceType)
|
||||
{
|
||||
return new ExpressionArgumentException(
|
||||
expression,
|
||||
string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentInvalidResourceType, expression, providerNamespace, resourceType)
|
||||
);
|
||||
}
|
||||
|
||||
#endregion Exceptions
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PSRule.Rules.Azure.Data.Template
|
||||
|
@ -118,6 +120,25 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
public readonly ResourceGroupProperties Properties;
|
||||
}
|
||||
|
||||
public sealed class ResourceProvider
|
||||
{
|
||||
internal ResourceProvider()
|
||||
{
|
||||
Types = new Dictionary<string, ResourceProviderType>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public Dictionary<string, ResourceProviderType> Types { get; }
|
||||
}
|
||||
|
||||
public sealed class ResourceProviderType
|
||||
{
|
||||
public string ResourceType { get; set; }
|
||||
|
||||
public string[] Locations { get; set; }
|
||||
|
||||
public string[] ApiVersions { get; set; }
|
||||
}
|
||||
|
||||
public abstract class MockNode
|
||||
{
|
||||
protected MockNode(MockNode parent)
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PSRule.Rules.Azure.Resources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace PSRule.Rules.Azure.Data.Template
|
||||
|
@ -37,6 +40,7 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
{
|
||||
private readonly Stack<JObject> _Deployment;
|
||||
private JObject _Parameters;
|
||||
private readonly Dictionary<string, ResourceProvider> _Providers;
|
||||
|
||||
internal TemplateContext()
|
||||
{
|
||||
|
@ -47,6 +51,7 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
ResourceGroup = new ResourceGroup();
|
||||
Subscription = new Subscription();
|
||||
_Deployment = new Stack<JObject>();
|
||||
_Providers = ReadProviders();
|
||||
}
|
||||
|
||||
internal TemplateContext(Subscription subscription, ResourceGroup resourceGroup)
|
||||
|
@ -230,6 +235,33 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Dictionary<string, ResourceProvider> ReadProviders()
|
||||
{
|
||||
var providersFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "providers.json");
|
||||
if (!File.Exists(providersFile))
|
||||
return null;
|
||||
|
||||
var json = File.ReadAllText(providersFile);
|
||||
var settings = new JsonSerializerSettings();
|
||||
settings.Converters.Add(new ResourceProviderConverter());
|
||||
var result = JsonConvert.DeserializeObject<Dictionary<string, ResourceProvider>>(json, settings);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal ResourceProviderType[] GetResourceType(string providerNamespace, string resourceType)
|
||||
{
|
||||
if (_Providers == null || _Providers.Count == 0 || !_Providers.TryGetValue(providerNamespace, out ResourceProvider provider))
|
||||
return Array.Empty<ResourceProviderType>();
|
||||
|
||||
if (resourceType == null)
|
||||
return provider.Types.Values.ToArray();
|
||||
|
||||
if (!provider.Types.ContainsKey(resourceType))
|
||||
return Array.Empty<ResourceProviderType>();
|
||||
|
||||
return new ResourceProviderType[] { provider.Types[resourceType] };
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class LazyValue
|
||||
|
|
|
@ -78,6 +78,24 @@ namespace PSRule.Rules.Azure.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The resource type '{1}/{2}' for '{0}' is not known..
|
||||
/// </summary>
|
||||
internal static string ArgumentInvalidResourceType {
|
||||
get {
|
||||
return ResourceManager.GetString("ArgumentInvalidResourceType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The argument '{0}' for '{1}' is not a valid string..
|
||||
/// </summary>
|
||||
internal static string ArgumentInvalidString {
|
||||
get {
|
||||
return ResourceManager.GetString("ArgumentInvalidString", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The number of arguments '{1}' is not within the allowed range for '{0}'..
|
||||
/// </summary>
|
||||
|
|
|
@ -123,6 +123,12 @@
|
|||
<data name="ArgumentInvalidInteger" xml:space="preserve">
|
||||
<value>The argument '{0}' for '{1}' is not a valid integer.</value>
|
||||
</data>
|
||||
<data name="ArgumentInvalidResourceType" xml:space="preserve">
|
||||
<value>The resource type '{1}/{2}' for '{0}' is not known.</value>
|
||||
</data>
|
||||
<data name="ArgumentInvalidString" xml:space="preserve">
|
||||
<value>The argument '{0}' for '{1}' is not a valid string.</value>
|
||||
</data>
|
||||
<data name="ArgumentsOutOfRange" xml:space="preserve">
|
||||
<value>The number of arguments '{1}' is not within the allowed range for '{0}'.</value>
|
||||
</data>
|
||||
|
|
|
@ -67,38 +67,38 @@ Describe 'Azure Quickstart Templates' -Tag integration {
|
|||
# Skips templates because they are not complete
|
||||
$skipTemplates = @(
|
||||
"100-blank-template"
|
||||
"101-hdinsight-custom-ambari-db"
|
||||
"101-sql-vm-aglistener-setup"
|
||||
"101-sql-vm-ag-setup"
|
||||
"101-vm-windows-copy-datadisks"
|
||||
"201-api-management-logs-oms-integration"
|
||||
"201-data-factory-ftp-hive-blob"
|
||||
"201-list-storage-keys-windows-vm"
|
||||
"201-vm-msi"
|
||||
"201-vm-win-iis-app-ssl"
|
||||
"201-vmss-win-iis-app-ssl"
|
||||
"301-custom-images-at-scale"
|
||||
"301-drupal8-vmss-glusterfs-mysql"
|
||||
"301-jenkins-aptly-spinnaker-vmss"
|
||||
"301-vm-sql-full-autobackup-autopatching-keyvault"
|
||||
"blockchain"
|
||||
"chef-ha-cluster"
|
||||
"couchbase"
|
||||
"hdinsight-genomics-adam"
|
||||
"hdinsight-linux-run-script-action"
|
||||
"intel-lustre-client-server"
|
||||
"sql-server-2014-alwayson-existing-vnet-and-ad"
|
||||
"kemp-loadmaster-multinic"
|
||||
"memcached-multi-vm-ubuntu"
|
||||
"oms-azure-storage-analytics-solution"
|
||||
"openedx-devstack-ubuntu"
|
||||
"opensis-cluster-ubuntu"
|
||||
"rhel-3tier-iaas"
|
||||
"slurm-on-sles12-hpc"
|
||||
"traffic-manager-demo-setup"
|
||||
"vsts-fullbuild-redhat-vm"
|
||||
"vsts-fullbuild-ubuntu-vm"
|
||||
"vsts-minbuildjava-ubuntu-vm"
|
||||
# "101-hdinsight-custom-ambari-db"
|
||||
# "101-sql-vm-aglistener-setup"
|
||||
# "101-sql-vm-ag-setup"
|
||||
# "101-vm-windows-copy-datadisks"
|
||||
# "201-api-management-logs-oms-integration"
|
||||
# "201-data-factory-ftp-hive-blob"
|
||||
# "201-list-storage-keys-windows-vm"
|
||||
# "201-vm-msi"
|
||||
# "201-vm-win-iis-app-ssl"
|
||||
# "201-vmss-win-iis-app-ssl"
|
||||
# "301-custom-images-at-scale"
|
||||
# "301-drupal8-vmss-glusterfs-mysql"
|
||||
# "301-jenkins-aptly-spinnaker-vmss"
|
||||
# "301-vm-sql-full-autobackup-autopatching-keyvault"
|
||||
# "blockchain"
|
||||
# "chef-ha-cluster"
|
||||
# "couchbase"
|
||||
# "hdinsight-genomics-adam"
|
||||
# "hdinsight-linux-run-script-action"
|
||||
# "intel-lustre-client-server"
|
||||
# "sql-server-2014-alwayson-existing-vnet-and-ad"
|
||||
# "kemp-loadmaster-multinic"
|
||||
# "memcached-multi-vm-ubuntu"
|
||||
# "oms-azure-storage-analytics-solution"
|
||||
# "openedx-devstack-ubuntu"
|
||||
# "opensis-cluster-ubuntu"
|
||||
# "rhel-3tier-iaas"
|
||||
# "slurm-on-sles12-hpc"
|
||||
# "traffic-manager-demo-setup"
|
||||
# "vsts-fullbuild-redhat-vm"
|
||||
# "vsts-fullbuild-ubuntu-vm"
|
||||
# "vsts-minbuildjava-ubuntu-vm"
|
||||
)
|
||||
foreach ($sample in (Get-Sample -Path $clonePath)) {
|
||||
if ($sample.Name -in $skipTemplates) {
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace PSRule.Rules.Azure
|
|||
private const string TRAIT_ARRAY = "Array";
|
||||
private const string TRAIT_LOGICAL = "Logical";
|
||||
private const string TRAIT_COMPARISON = "Comparison";
|
||||
private const string TRAIT_DATE = "Date";
|
||||
private const string TRAIT_DEPLOYMENT = "Deployment";
|
||||
private const string TRAIT_NUMERIC = "Numeric";
|
||||
private const string TRAIT_STRING = "String";
|
||||
|
@ -307,8 +308,8 @@ namespace PSRule.Rules.Azure
|
|||
Assert.Throws<ExpressionArgumentException>(() => Functions.Range(context, null));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Range(context, new object[] { }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Range(context, new object[] { 1 }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.Range(context, new object[] { "one", "two" }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.Range(context, new object[] { 1, "0" }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Range(context, new object[] { "one", "two" }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Range(context, new object[] { 1, "0" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -326,7 +327,7 @@ namespace PSRule.Rules.Azure
|
|||
Assert.Equal("one two three", actual3);
|
||||
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Skip(context, new object[] { "one two three" }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.Skip(context, new object[] { "one two three", "0" }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Skip(context, new object[] { "one two three", "0" }));
|
||||
|
||||
// Array
|
||||
var actual4 = Functions.Skip(context, new object[] { JArray.Parse("[ \"one\", \"two\", \"three\" ]"), 2 }) as JArray;
|
||||
|
@ -337,8 +338,8 @@ namespace PSRule.Rules.Azure
|
|||
Assert.Equal(3, actual6.Count);
|
||||
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Skip(context, new object[] { JArray.Parse("[ \"one\", \"two\", \"three\" ]") }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.Skip(context, new object[] { JArray.Parse("[ \"one\", \"two\", \"three\" ]"), "0" }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.Skip(context, new object[] { 1, "0" }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Skip(context, new object[] { JArray.Parse("[ \"one\", \"two\", \"three\" ]"), "0" }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Skip(context, new object[] { 1, "0" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -427,6 +428,33 @@ namespace PSRule.Rules.Azure
|
|||
// TODO: Improve test cases
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait(TRAIT, TRAIT_RESOURCE)]
|
||||
public void Providers()
|
||||
{
|
||||
var context = GetContext();
|
||||
|
||||
var actual1 = Functions.Providers(context, new object[] { "Microsoft.Web", "sites" }) as ResourceProviderType;
|
||||
Assert.NotNull(actual1);
|
||||
Assert.Equal("sites", actual1.ResourceType);
|
||||
Assert.Equal("2020-06-01", actual1.ApiVersions[0]);
|
||||
Assert.Equal("South Central US", actual1.Locations[0]);
|
||||
|
||||
var actual2 = Functions.Providers(context, new object[] { "Microsoft.Web" }) as ResourceProviderType[];
|
||||
Assert.NotNull(actual1);
|
||||
Assert.Equal(51, actual2.Length);
|
||||
|
||||
var actual3 = Functions.Providers(context, new object[] { "microsoft.web", "Sites" }) as ResourceProviderType;
|
||||
Assert.NotNull(actual3);
|
||||
Assert.Equal("sites", actual3.ResourceType);
|
||||
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Providers(context, null));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Providers(context, new object[] { 1 }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Providers(context, new object[] { "Microsoft.Web", 1 }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Providers(context, new object[] { "Microsoft.Web", "n/a" }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Providers(context, new object[] { "n/a", "n/a" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait(TRAIT, TRAIT_RESOURCE)]
|
||||
public void Reference()
|
||||
|
@ -712,6 +740,46 @@ namespace PSRule.Rules.Azure
|
|||
|
||||
#endregion Comparison
|
||||
|
||||
#region Date
|
||||
|
||||
[Fact]
|
||||
[Trait(TRAIT, TRAIT_DATE)]
|
||||
public void DateTimeAdd()
|
||||
{
|
||||
var context = GetContext();
|
||||
var utc = DateTime.Parse("2020-04-07 14:53:14Z", new CultureInfo("en-US"));
|
||||
|
||||
var actual1 = DateTime.Parse(Functions.DateTimeAdd(context, new object[] { utc.ToString("u"), "P3Y" }) as string, new CultureInfo("en-US"));
|
||||
var actual2 = DateTime.Parse(Functions.DateTimeAdd(context, new object[] { utc.ToString("u"), "-P9D" }) as string, new CultureInfo("en-US"));
|
||||
var actual3 = DateTime.Parse(Functions.DateTimeAdd(context, new object[] { utc.ToString("u"), "PT1H" }) as string, new CultureInfo("en-US"));
|
||||
|
||||
Assert.Equal(utc.AddYears(3), actual1.ToUniversalTime());
|
||||
Assert.Equal(utc.AddDays(-9), actual2.ToUniversalTime());
|
||||
Assert.Equal(utc.AddHours(1), actual3.ToUniversalTime());
|
||||
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.DateTimeAdd(context, null));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.DateTimeAdd(context, new object[] { }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.DateTimeAdd(context, new object[] { 1, 2 }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.DateTimeAdd(context, new object[] { utc.ToString("u"), 2 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait(TRAIT, TRAIT_DATE)]
|
||||
public void UtcNow()
|
||||
{
|
||||
var context = GetContext();
|
||||
var utc = DateTime.UtcNow;
|
||||
|
||||
var actual1 = Functions.UtcNow(context, new object[] { }) as string;
|
||||
var actual2 = Functions.UtcNow(context, new object[] { "d" }) as string;
|
||||
var actual3 = Functions.UtcNow(context, new object[] { "M d" }) as string;
|
||||
Assert.Matches("[0-9]{8}T[0-9]{6}Z", actual1);
|
||||
Assert.Equal(utc.ToString("d", new CultureInfo("en-US")), actual2);
|
||||
Assert.Equal(utc.ToString("M d", new CultureInfo("en-US")), actual3);
|
||||
}
|
||||
|
||||
#endregion Date
|
||||
|
||||
#region Logical
|
||||
|
||||
[Fact]
|
||||
|
@ -920,7 +988,7 @@ namespace PSRule.Rules.Azure
|
|||
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Base64(context, null));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Base64(context, new object[] { }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.Base64(context, new object[] { 1 }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Base64(context, new object[] { 1 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -935,7 +1003,7 @@ namespace PSRule.Rules.Azure
|
|||
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Base64ToJson(context, null));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Base64ToJson(context, new object[] { }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.Base64ToJson(context, new object[] { 1 }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Base64ToJson(context, new object[] { 1 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -949,7 +1017,7 @@ namespace PSRule.Rules.Azure
|
|||
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Base64ToString(context, null));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Base64ToString(context, new object[] { }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.Base64ToString(context, new object[] { 1 }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Base64ToString(context, new object[] { 1 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -1244,7 +1312,7 @@ namespace PSRule.Rules.Azure
|
|||
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Uri(context, null));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Uri(context, new object[] { }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.Uri(context, new object[] { "http://contoso.org/firstpath", 2 }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.Uri(context, new object[] { "http://contoso.org/firstpath", 2 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -1258,7 +1326,7 @@ namespace PSRule.Rules.Azure
|
|||
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.UriComponent(context, null));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.UriComponent(context, new object[] { }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.UriComponent(context, new object[] { 2 }));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.UriComponent(context, new object[] { 2 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -1272,22 +1340,7 @@ namespace PSRule.Rules.Azure
|
|||
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.UriComponentToString(context, null));
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.UriComponentToString(context, new object[] { }));
|
||||
Assert.Throws<ArgumentException>(() => Functions.UriComponentToString(context, new object[] { 2 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait(TRAIT, TRAIT_STRING)]
|
||||
public void UtcNow()
|
||||
{
|
||||
var context = GetContext();
|
||||
var utc = DateTime.UtcNow;
|
||||
|
||||
var actual1 = Functions.UtcNow(context, new object[] { }) as string;
|
||||
var actual2 = Functions.UtcNow(context, new object[] { "d" }) as string;
|
||||
var actual3 = Functions.UtcNow(context, new object[] { "M d" }) as string;
|
||||
Assert.Matches("[0-9]{8}T[0-9]{6}Z", actual1);
|
||||
Assert.Equal(utc.ToString("d", new CultureInfo("en-US")), actual2);
|
||||
Assert.Equal(utc.ToString("M d", new CultureInfo("en-US")), actual3);
|
||||
Assert.Throws<ExpressionArgumentException>(() => Functions.UriComponentToString(context, new object[] { 2 }));
|
||||
}
|
||||
|
||||
#endregion String
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="providers.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources.Parameters.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче