From bca3a9a7c1cf1277bc9bd92a5cab5d29883484a5 Mon Sep 17 00:00:00 2001 From: polatengin Date: Fri, 14 Jul 2023 18:25:53 +0000 Subject: [PATCH] adding getSecret helper to az namespace for bicepparam files --- .../Emit/ParamsFileWriterTests.cs | 40 +- .../EvaluationTests.cs | 50 ++- .../parameters.diagnostics.bicepparam | 3 +- .../Diagnostics/DiagnosticBuilder.cs | 13 +- .../Emit/FunctionPlacementValidatorVisitor.cs | 25 +- src/Bicep.Core/Emit/ParametersJsonWriter.cs | 50 ++- .../Semantics/Namespaces/AzNamespaceType.cs | 348 ++++++++++-------- .../Namespaces/DefaultNamespaceProvider.cs | 2 +- .../Semantics/Namespaces/NamespaceResolver.cs | 9 +- src/Bicep.Core/TypeSystem/FunctionFlags.cs | 5 + 10 files changed, 359 insertions(+), 186 deletions(-) diff --git a/src/Bicep.Core.IntegrationTests/Emit/ParamsFileWriterTests.cs b/src/Bicep.Core.IntegrationTests/Emit/ParamsFileWriterTests.cs index 54c3b057936..8ed36176bf1 100644 --- a/src/Bicep.Core.IntegrationTests/Emit/ParamsFileWriterTests.cs +++ b/src/Bicep.Core.IntegrationTests/Emit/ParamsFileWriterTests.cs @@ -33,7 +33,45 @@ param myParam string ")] [DataRow(@" using 'main.bicep' - +param myParam = getSecret('', '', '', '')", @" +{ + ""$schema"": ""https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#"", + ""contentVersion"": ""1.0.0.0"", + ""parameters"": { + ""myParam"": { + ""reference"": { + ""keyVault"": { + ""id"": ""/subscriptions//resourceGroups//providers/Microsoft.KeyVault/vaults/"" + }, + ""secretName"": """" + } + } + } +}", @" +param myParam string +")] + [DataRow(@" +using 'main.bicep' +param myParam = getSecret('', '', '', '', '')", @" +{ + ""$schema"": ""https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#"", + ""contentVersion"": ""1.0.0.0"", + ""parameters"": { + ""myParam"": { + ""reference"": { + ""keyVault"": { + ""id"": ""/subscriptions//resourceGroups//providers/Microsoft.KeyVault/vaults/"" + }, + ""secretName"": """", + ""secretVersion"": """" + } + } + } +}", @" +param myParam string +")] + [DataRow(@" +using 'main.bicep' param myParam = 1", @" { ""$schema"": ""https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#"", diff --git a/src/Bicep.Core.IntegrationTests/EvaluationTests.cs b/src/Bicep.Core.IntegrationTests/EvaluationTests.cs index 3044efc3c1e..d87b8d3b3a0 100644 --- a/src/Bicep.Core.IntegrationTests/EvaluationTests.cs +++ b/src/Bicep.Core.IntegrationTests/EvaluationTests.cs @@ -86,14 +86,14 @@ are not [TestMethod] public void ResourceId_expressions_are_evaluated_successfully() { - var bicepparamText = @" + var bicepparamText = @" using 'main.bicep' param parentName = 'myParent' param childName = 'myChild' "; - var bicepTemplateText = @" + var bicepTemplateText = @" param parentName string param childName string @@ -362,7 +362,7 @@ public void Join_function_evaluation_works() [TestMethod] public void indexof_contains_function_evaluation_works() - { + { var bicepparamText = @" using 'main.bicep' @@ -437,7 +437,7 @@ param inputArray array [TestMethod] public void List_comprehension_function_evaluation_works() - { + { var bicepparamText = @" using 'main.bicep' @@ -763,7 +763,7 @@ public void Issue8782() /// [TestMethod] public void Issue8782_2() - { + { var bicepparamText = @" using 'main.bicep' @@ -872,12 +872,12 @@ public void Issue8798() [TestMethod] public void Module_with_unknown_resourcetype_as_parameter_and_output_has_diagnostics() - { + { var bicepparamText = @" using 'main.bicep' param useMod1 = true -"; +"; var bicepTemplateText = @" param useMod1 bool @@ -917,12 +917,13 @@ param bar string var result = CompilationHelper.Compile(("main.bicep", bicepTemplateText), ("module.bicep", bicepModuleText)); - var evaluated = TemplateEvaluator.Evaluate(result.Template, parameters, config => config with { + var evaluated = TemplateEvaluator.Evaluate(result.Template, parameters, config => config with + { OnReferenceFunc = (resourceId, apiVersion, fullBody) => { - var id = ResourceGroupLevelResourceId.Parse(resourceId); - var barVal = id.FormatName() == "test" ? "abc" : "def"; - return JToken.Parse(@"{ + var id = ResourceGroupLevelResourceId.Parse(resourceId); + var barVal = id.FormatName() == "test" ? "abc" : "def"; + return JToken.Parse(@"{ ""outputs"": { ""foo"": { ""value"": { @@ -976,5 +977,32 @@ public void Safe_dereferences_are_evaluated_successfully() evaluated.Should().HaveValueAtPath("$.outputs['properties'].value.doesntExistArrayAccess", JValue.CreateNull()); } } + + [TestMethod] + public void az_getsecret_functions_are_evaluated_successfully() + { + var bicepTemplateText = @" +param param1 object +output output1 object = param1 +"; + + var bicepparamText = @" +using 'main.bicep' +param param1 = { reference: 'param1' } +"; + + var (parameters, _, _) = CompilationHelper.CompileParams(("parameters.bicepparam", bicepparamText), ("main.bicep", bicepTemplateText)); + + var (template, diagnostics, _) = CompilationHelper.Compile(bicepTemplateText); + + using (new AssertionScope()) + { + var evaluated = TemplateEvaluator.Evaluate(template, parameters); + + diagnostics.Should().NotHaveAnyDiagnostics(); + + evaluated.Should().HaveValueAtPath("$.outputs['output1'].value.reference", $"param1"); + } + } } } diff --git a/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.diagnostics.bicepparam b/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.diagnostics.bicepparam index 10f45a812a3..c56261af7f4 100644 --- a/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.diagnostics.bicepparam +++ b/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.diagnostics.bicepparam @@ -130,7 +130,6 @@ param myObj = { environment: environment() //@[15:26) [BCP057 (Error)] The name "environment" does not exist in the current context. (CodeDescription: none) |environment| azNs: az -//@[08:10) [BCP057 (Error)] The name "az" does not exist in the current context. (CodeDescription: none) |az| azNsFunc: az.providers('Microsoft.Compute') -//@[12:14) [BCP057 (Error)] The name "az" does not exist in the current context. (CodeDescription: none) |az| +//@[15:24) [BCP107 (Error)] The function "providers" does not exist in namespace "az". (CodeDescription: none) |providers| } diff --git a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs index acdda601a1c..1b9f997c251 100644 --- a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs +++ b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs @@ -1426,7 +1426,7 @@ public ErrorDiagnostic UnknownModuleReferenceScheme(string badScheme, ImmutableA "BCP243", "Parentheses must contain exactly one expression."); - public ErrorDiagnostic LambdaExpectedArgCountMismatch(TypeSymbol lambdaType, int expectedArgCount, int actualArgCount) => new ( + public ErrorDiagnostic LambdaExpectedArgCountMismatch(TypeSymbol lambdaType, int expectedArgCount, int actualArgCount) => new( TextSpan, "BCP244", $"Expected lambda expression of type \"{lambdaType}\" with {expectedArgCount} arguments but received {actualArgCount} arguments."); @@ -1920,9 +1920,9 @@ public ErrorDiagnostic RuntimeValueNotAllowedInFunctionDeclaration(string? acces "BCP341", $"This expression is being used inside a function declaration, which requires a value that can be calculated at the start of the deployment.{variableDependencyChainClause}{accessiblePropertiesClause}"); } - + public ErrorDiagnostic UserDefinedTypesNotAllowedInFunctionDeclaration() => new( - TextSpan, + TextSpan, "BCP342", $"""User-defined types are not supported in user-defined function parameters or outputs."""); @@ -1930,7 +1930,12 @@ public ErrorDiagnostic RuntimeValueNotAllowedInFunctionDeclaration(string? acces TextSpan, "BCP343", $@"Using a func declaration statement requires enabling EXPERIMENTAL feature ""{nameof(ExperimentalFeaturesEnabled.UserDefinedFunctions)}""."); - + + public ErrorDiagnostic FunctionOnlyValidWithDirectAssignment(string functionName) => new( + TextSpan, + "BCP344", + $"Function \"{functionName}\" is not valid at this location. It can only be used when directly assigning to a parameter."); + public ErrorDiagnostic TestDeclarationMustReferenceBicepTest() => new( TextSpan, "BCP345", diff --git a/src/Bicep.Core/Emit/FunctionPlacementValidatorVisitor.cs b/src/Bicep.Core/Emit/FunctionPlacementValidatorVisitor.cs index 3b2ded0c288..0f9d652ab3c 100644 --- a/src/Bicep.Core/Emit/FunctionPlacementValidatorVisitor.cs +++ b/src/Bicep.Core/Emit/FunctionPlacementValidatorVisitor.cs @@ -80,15 +80,26 @@ public override void VisitFunctionCallSyntax(FunctionCallSyntax syntax) private void VerifyModuleSecureParameterFunctionPlacement(FunctionCallSyntaxBase syntax) { - if (semanticModel.GetSymbolInfo(syntax) is FunctionSymbol functionSymbol && functionSymbol.FunctionFlags.HasFlag(FunctionFlags.ModuleSecureParameterOnly)) + if (semanticModel.GetSymbolInfo(syntax) is FunctionSymbol functionSymbol) { - // we can check placement only for funtions that were matched and has a proper placement flag - var (_, levelUpSymbol) = syntaxRecorder.Skip(1).SkipWhile(x => x.syntax is TernaryOperationSyntax).FirstOrDefault(); - if (!(elementsRecorder.TryPeek(out var head) && head == VisitedElement.ModuleParams) - || levelUpSymbol is not PropertySymbol propertySymbol - || !propertySymbol.Type.ValidationFlags.HasFlag(TypeSymbolValidationFlags.IsSecure)) + if (functionSymbol.FunctionFlags.HasFlag(FunctionFlags.ModuleSecureParameterOnly)) { - diagnosticWriter.Write(DiagnosticBuilder.ForPosition(syntax).FunctionOnlyValidInModuleSecureParameterAssignment(functionSymbol.Name)); + // we can check placement only for funtions that were matched and has a proper placement flag + var (_, levelUpSymbol) = syntaxRecorder.Skip(1).SkipWhile(x => x.syntax is TernaryOperationSyntax).FirstOrDefault(); + if (!(elementsRecorder.TryPeek(out var head) && head == VisitedElement.ModuleParams) + || levelUpSymbol is not PropertySymbol propertySymbol + || !propertySymbol.Type.ValidationFlags.HasFlag(TypeSymbolValidationFlags.IsSecure)) + { + diagnosticWriter.Write(DiagnosticBuilder.ForPosition(syntax).FunctionOnlyValidInModuleSecureParameterAssignment(functionSymbol.Name)); + } + } + if (functionSymbol.FunctionFlags.HasFlag(FunctionFlags.DirectAssignment)) + { + var (_, levelUpSymbol) = syntaxRecorder.Skip(1).SkipWhile(x => x.syntax is TernaryOperationSyntax).FirstOrDefault(); + if (levelUpSymbol is null) + { + diagnosticWriter.Write(DiagnosticBuilder.ForPosition(syntax).FunctionOnlyValidWithDirectAssignment(functionSymbol.Name)); + } } } } diff --git a/src/Bicep.Core/Emit/ParametersJsonWriter.cs b/src/Bicep.Core/Emit/ParametersJsonWriter.cs index 97a6f5b03cd..b88829c81d7 100644 --- a/src/Bicep.Core/Emit/ParametersJsonWriter.cs +++ b/src/Bicep.Core/Emit/ParametersJsonWriter.cs @@ -19,7 +19,7 @@ public ParametersJsonWriter(SemanticModel model) } public void Write(JsonTextWriter writer) => GenerateTemplate().WriteTo(writer); - + public JToken GenerateTemplate() { using var stringWriter = new StringWriter(); @@ -36,16 +36,50 @@ public JToken GenerateTemplate() jsonWriter.WritePropertyName("parameters"); jsonWriter.WriteStartObject(); - foreach (var parameter in model.Root.ParameterAssignments) + foreach (var assignment in model.Root.ParameterAssignments) { - jsonWriter.WritePropertyName(parameter.Name); + jsonWriter.WritePropertyName(assignment.Name); + + var parameter = model.EmitLimitationInfo.ParameterAssignments[assignment]; + + var propertyName = parameter.Type switch + { + JTokenType.Object => (parameter?.First as JProperty)?.Name, + JTokenType.Array => (parameter as JArray).ToJson(), + _ => parameter.Value() + }; + var isReferenceProperty = propertyName == "reference"; + var isObjectType = assignment.Type.TypeKind == TypeSystem.TypeKind.Object; - jsonWriter.WriteStartObject(); + if (parameter?.Type == JTokenType.Object) + { + if (!isReferenceProperty || (isReferenceProperty && isObjectType)) + { + jsonWriter.WriteStartObject(); + jsonWriter.WritePropertyName("value"); + } + } + else + { + jsonWriter.WriteStartObject(); + jsonWriter.WritePropertyName("value"); + } - jsonWriter.WritePropertyName("value"); - model.EmitLimitationInfo.ParameterAssignments[parameter].WriteTo(jsonWriter); - jsonWriter.WriteEndObject(); + parameter?.WriteTo(jsonWriter); + + if (parameter?.Type == JTokenType.Object) + { + if (!isReferenceProperty || (isReferenceProperty && isObjectType)) + { + jsonWriter.WriteEndObject(); + } + } + else + { + jsonWriter.WriteEndObject(); + } } + jsonWriter.WriteEndObject(); jsonWriter.WriteEndObject(); @@ -54,4 +88,4 @@ public JToken GenerateTemplate() return content.FromJson(); } -} \ No newline at end of file +} diff --git a/src/Bicep.Core/Semantics/Namespaces/AzNamespaceType.cs b/src/Bicep.Core/Semantics/Namespaces/AzNamespaceType.cs index 7be624476e4..13b73ef5d50 100644 --- a/src/Bicep.Core/Semantics/Namespaces/AzNamespaceType.cs +++ b/src/Bicep.Core/Semantics/Namespaces/AzNamespaceType.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Azure.Deployments.Core.Definitions.Identifiers; using Bicep.Core.Diagnostics; using Bicep.Core.Extensions; using Bicep.Core.FileSystem; @@ -10,12 +11,14 @@ using Bicep.Core.Syntax; using Bicep.Core.TypeSystem; using Bicep.Core.TypeSystem.Az; +using Bicep.Core.Workspaces; namespace Bicep.Core.Semantics.Namespaces { public static class AzNamespaceType { public const string BuiltInName = "az"; + public const string GetSecretFunctionName = "getSecret"; public static NamespaceSettings Settings { get; } = new( IsSingleton: true, @@ -26,7 +29,8 @@ public static class AzNamespaceType private static FunctionOverload.ResultBuilderDelegate AddDiagnosticsAndReturnResult(TypeSymbol returnType, DiagnosticBuilder.DiagnosticBuilderDelegate writeDiagnostic) { - return (binder, fileResolver, diagnostics, functionCall, argumentTypes) => { + return (binder, fileResolver, diagnostics, functionCall, argumentTypes) => + { diagnostics.Write(functionCall.Name, writeDiagnostic); return new(returnType); @@ -285,7 +289,7 @@ private static ObjectType GetDeploymentReturnType(ResourceScope targetScope) ResourceScope.Tenant | ResourceScope.ManagementGroup | ResourceScope.Subscription | ResourceScope.ResourceGroup); } - private static IEnumerable GetAzOverloads(ResourceScope resourceScope) + private static IEnumerable GetAzOverloads(ResourceScope resourceScope, BicepSourceFileKind sourceFileKind) { foreach (var (functionOverload, allowedScopes) in GetScopeFunctions()) { @@ -298,157 +302,209 @@ private static IEnumerable GetAzOverloads(ResourceScope resour // TODO: add banned function to explain why a given function isn't available } - // TODO: Add schema for return type - yield return new FunctionOverloadBuilder("deployment") - .WithReturnType(GetDeploymentReturnType(resourceScope)) - .WithGenericDescription("Returns information about the current deployment operation.") - .Build(); - - yield return new FunctionOverloadBuilder("environment") - .WithReturnType(GetEnvironmentReturnType()) - .WithGenericDescription("Returns information about the Azure environment used for deployment.") - .Build(); - - // TODO: This is based on docs. Verify - // the resourceId function relies on leading optional parameters that are disambiguated at runtime - // modeling this as multiple overload with all possible permutations of the leading parameters - const string resourceIdDescription = "Returns the unique identifier of a resource. You use this function when the resource name is ambiguous or not provisioned within the same template. The format of the returned identifier varies based on whether the deployment happens at the scope of a resource group, subscription, management group, or tenant."; - yield return new FunctionOverloadBuilder("resourceId") - .WithReturnType(LanguageConstants.String) - .WithGenericDescription(resourceIdDescription) - .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") - .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") - .Build(); - - yield return new FunctionOverloadBuilder("resourceId") - .WithReturnType(LanguageConstants.String) - .WithGenericDescription(resourceIdDescription) - .WithRequiredParameter("subscriptionId", LanguageConstants.String, "The subscription ID") - .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") - .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") - .Build(); - - yield return new FunctionOverloadBuilder("resourceId") - .WithReturnType(LanguageConstants.String) - .WithGenericDescription(resourceIdDescription) - .WithRequiredParameter("resourceGroupName", LanguageConstants.String, "The resource group name") - .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") - .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") - .Build(); - - yield return new FunctionOverloadBuilder("resourceId") - .WithReturnType(LanguageConstants.String) - .WithGenericDescription(resourceIdDescription) - .WithRequiredParameter("subscriptionId", LanguageConstants.String, "The subscription ID") - .WithRequiredParameter("resourceGroupName", LanguageConstants.String, "The resource group name") - .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") - .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") - .Build(); - - // the subscriptionResourceId function relies on leading optional parameters that are disambiguated at runtime - // modeling this as multiple overload with all possible permutations of the leading parameters - const string subscriptionResourceIdDescription = "Returns the unique identifier for a resource deployed at the subscription level."; - yield return new FunctionOverloadBuilder("subscriptionResourceId") - .WithReturnType(LanguageConstants.String) - .WithGenericDescription(subscriptionResourceIdDescription) - .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") - .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") - .Build(); - - yield return new FunctionOverloadBuilder("subscriptionResourceId") - .WithReturnType(LanguageConstants.String) - .WithGenericDescription(subscriptionResourceIdDescription) - .WithRequiredParameter("subscriptionId", LanguageConstants.String, "The subscription ID") - .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") - .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") - .Build(); - - yield return new FunctionOverloadBuilder("tenantResourceId") - .WithReturnType(LanguageConstants.String) - .WithGenericDescription("Returns the unique identifier for a resource deployed at the tenant level.") - .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") - .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") - .Build(); - - yield return new FunctionOverloadBuilder("extensionResourceId") - .WithReturnType(LanguageConstants.String) - // .WithGenericDescription("Returns the resource ID for an extension resource, which is a resource type that is applied to another resource to add to its capabilities.") - .WithGenericDescription("Returns the resource ID for an [extension](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/extension-resource-types) resource, which is a resource type that is applied to another resource to add to its capabilities.") - .WithRequiredParameter("resourceId", LanguageConstants.String, "The resource ID for the resource that the extension resource is applied to") - .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of the extension resource including resource provider namespace") - .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The extension resource name segment") - .Build(); - - const string managementGroupResourceIdDescription = "Returns the unique identifier for a resource deployed at the management group level."; - yield return new FunctionOverloadBuilder("managementGroupResourceId") - .WithReturnType(LanguageConstants.String) - .WithGenericDescription(managementGroupResourceIdDescription) - .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") - .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") - .Build(); - - yield return new FunctionOverloadBuilder("managementGroupResourceId") - .WithReturnType(LanguageConstants.String) - .WithGenericDescription(managementGroupResourceIdDescription) - .WithRequiredParameter("managementGroupId", LanguageConstants.String, "The management group ID") - .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") - .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") - .Build(); - - const string providersDescription = "Returns information about a resource provider and its supported resource types. If you don't provide a resource type, the function returns all the supported types for the resource provider."; - yield return new FunctionOverloadBuilder("providers") - .WithReturnResultBuilder(AddDiagnosticsAndReturnResult(GetProvidersSingleProviderReturnType(), x => x.DeprecatedProvidersFunction("providers")), GetProvidersSingleProviderReturnType()) - .WithGenericDescription(providersDescription) - .WithRequiredParameter("providerNamespace", LanguageConstants.String, "the namespace of the provider") - .Build(); - - yield return new FunctionOverloadBuilder("providers") - .WithReturnResultBuilder(AddDiagnosticsAndReturnResult(GetProvidersSingleResourceReturnType(), x => x.DeprecatedProvidersFunction("providers")), GetProvidersSingleResourceReturnType()) - .WithGenericDescription(providersDescription) - .WithRequiredParameter("providerNamespace", LanguageConstants.String, "the namespace of the provider") - .WithRequiredParameter("resourceType", LanguageConstants.String, "The type of resource within the specified namespace") - .Build(); - - // TODO: return type is string[] - // TODO: Location param should be of location type if we ever add it - yield return new FunctionOverloadBuilder("pickZones") - .WithReturnType(LanguageConstants.Array) - .WithGenericDescription("Determines whether a resource type supports zones for a region.") - .WithRequiredParameter("providerNamespace", LanguageConstants.String, "The resource provider namespace for the resource type to check for zone support") - .WithRequiredParameter("resourceType", LanguageConstants.String, "The resource type to check for zone support") - .WithRequiredParameter("location", LanguageConstants.String, "The region to check for zone support") - .WithOptionalParameter("numberOfZones", LanguageConstants.Int, "The number of logical zones to return. The default is 1. The number must a positive integer from 1 to 3. Use 1 for single-zoned resources. For multi-zoned resources, the value must be less than or equal to the number of supported zones.") - .WithOptionalParameter("offset", LanguageConstants.Int, "The offset from the starting logical zone. The function returns an error if offset plus numberOfZones exceeds the number of supported zones.") - .Build(); - - // TODO: Change 'Full' to literal type after verifying in the runtime source - yield return new FunctionOverloadBuilder("reference") - .WithReturnType(LanguageConstants.Object) - .WithGenericDescription("Returns an object representing a resource's runtime state.") - .WithRequiredParameter("resourceNameOrIdentifier", LanguageConstants.String, "Name or unique identifier of a resource. When referencing a resource in the current template, provide only the resource name as a parameter. When referencing a previously deployed resource or when the name of the resource is ambiguous, provide the resource ID.") - .WithOptionalParameter("apiVersion", LanguageConstants.String, "API version of the specified resource. This parameter is required when the resource isn't provisioned within same template.") - .WithOptionalParameter("full", LanguageConstants.String, "Value that specifies whether to return the full resource object. If you don't specify 'Full', only the properties object of the resource is returned. The full object includes values such as the resource ID and location.") - .WithFlags(FunctionFlags.RequiresInlining) - .Build(); - - // TODO: Doc parameters need an update - yield return new FunctionWildcardOverloadBuilder("list*", AzConstants.ListWildcardFunctionRegex) - .WithReturnType(LanguageConstants.Any) - .WithGenericDescription("The syntax for this function varies by name of the list operations. Each implementation returns values for the resource type that supports a list operation. The operation name must start with list. Some common usages are `listKeys`, `listKeyValue`, and `listSecrets`.") - .WithRequiredParameter("resourceNameOrIdentifier", LanguageConstants.String, "Name or unique identifier of a resource. When referencing a resource in the current template, provide only the resource name as a parameter. When referencing a previously deployed resource or when the name of the resource is ambiguous, provide the resource ID.") - .WithRequiredParameter("apiVersion", LanguageConstants.String, "API version of resource runtime state. Typically, in the format, yyyy-mm-dd.") - .WithOptionalParameter("functionValues", LanguageConstants.Object, "An object that has values for the function. Only provide this object for functions that support receiving an object with parameter values, such as listAccountSas on a storage account. An example of passing function values is shown in this article.") - .WithFlags(FunctionFlags.RequiresInlining) - .Build(); + if (sourceFileKind == BicepSourceFileKind.ParamsFile) + { + yield return new FunctionOverloadBuilder(GetSecretFunctionName) + .WithReturnType(LanguageConstants.SecureString) + .WithGenericDescription("Retrieve a value from an Azure Key Vault at the start of a deployment. All arguments must be compile-time constants.") + .WithReturnResultBuilder((_, _, _, func, _) => + { + if ((func.Arguments[0].Expression as StringSyntax)?.TryGetLiteralValue() is not { } subscriptionId) + { + return new(ErrorType.Create(DiagnosticBuilder.ForPosition(func.Arguments[0]).CompileTimeConstantRequired())); + } + if ((func.Arguments[1].Expression as StringSyntax)?.TryGetLiteralValue() is not { } resourceGroupName) + { + return new(ErrorType.Create(DiagnosticBuilder.ForPosition(func.Arguments[1]).CompileTimeConstantRequired())); + } + if ((func.Arguments[2].Expression as StringSyntax)?.TryGetLiteralValue() is not { } keyVaultName) + { + return new(ErrorType.Create(DiagnosticBuilder.ForPosition(func.Arguments[2]).CompileTimeConstantRequired())); + } + if ((func.Arguments[3].Expression as StringSyntax)?.TryGetLiteralValue() is not { } secretName) + { + return new(ErrorType.Create(DiagnosticBuilder.ForPosition(func.Arguments[3]).CompileTimeConstantRequired())); + } + var kvResourceId = ResourceGroupLevelResourceId.Create(subscriptionId, resourceGroupName, "Microsoft.KeyVault", new[] { "vaults" }, new[] { keyVaultName }); + var referenceList = new List() { + ExpressionFactory.CreateObjectProperty("keyVault", ExpressionFactory.CreateObject(new[] { ExpressionFactory.CreateObjectProperty("id", ExpressionFactory.CreateStringLiteral(kvResourceId.FullyQualifiedId)) })), + ExpressionFactory.CreateObjectProperty("secretName", ExpressionFactory.CreateStringLiteral(secretName)) + }; + if (func.Arguments.Length > 4) + { + if ((func.Arguments[4].Expression as StringSyntax)?.TryGetLiteralValue() is not { } secretVersion) + { + return new(ErrorType.Create(DiagnosticBuilder.ForPosition(func.Arguments[4]).CompileTimeConstantRequired())); + } + referenceList.Add(ExpressionFactory.CreateObjectProperty("secretVersion", ExpressionFactory.CreateStringLiteral(secretVersion))); + } + var expression = ExpressionFactory.CreateObject(new[] { + ExpressionFactory.CreateObjectProperty("reference", ExpressionFactory.CreateObject(referenceList), func), + }, func); + return new(LanguageConstants.SecureString, expression); + }, LanguageConstants.SecureString) + .WithRequiredParameter("subscriptionId", LanguageConstants.String, "Id of the Subscription that has the target KeyVault") + .WithRequiredParameter("resourceGroupName", LanguageConstants.String, "Name of the Resource Group that has the target KeyVault") + .WithRequiredParameter("keyVaultName", LanguageConstants.String, "Name of the target KeyVault") + .WithRequiredParameter("secretName", LanguageConstants.String, "Name of the Secret") + .WithOptionalParameter("secretVersion", LanguageConstants.String, "Version of the Secret") + .WithFlags(FunctionFlags.DirectAssignment) + .Build(); + } + else + { + // TODO: Add schema for return type + yield return new FunctionOverloadBuilder("deployment") + .WithReturnType(GetDeploymentReturnType(resourceScope)) + .WithGenericDescription("Returns information about the current deployment operation.") + .Build(); + + yield return new FunctionOverloadBuilder("environment") + .WithReturnType(GetEnvironmentReturnType()) + .WithGenericDescription("Returns information about the Azure environment used for deployment.") + .Build(); + + // TODO: This is based on docs. Verify + // the resourceId function relies on leading optional parameters that are disambiguated at runtime + // modeling this as multiple overload with all possible permutations of the leading parameters + const string resourceIdDescription = "Returns the unique identifier of a resource. You use this function when the resource name is ambiguous or not provisioned within the same template. The format of the returned identifier varies based on whether the deployment happens at the scope of a resource group, subscription, management group, or tenant."; + yield return new FunctionOverloadBuilder("resourceId") + .WithReturnType(LanguageConstants.String) + .WithGenericDescription(resourceIdDescription) + .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") + .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") + .Build(); + + yield return new FunctionOverloadBuilder("resourceId") + .WithReturnType(LanguageConstants.String) + .WithGenericDescription(resourceIdDescription) + .WithRequiredParameter("subscriptionId", LanguageConstants.String, "The subscription ID") + .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") + .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") + .Build(); + + yield return new FunctionOverloadBuilder("resourceId") + .WithReturnType(LanguageConstants.String) + .WithGenericDescription(resourceIdDescription) + .WithRequiredParameter("resourceGroupName", LanguageConstants.String, "The resource group name") + .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") + .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") + .Build(); + + yield return new FunctionOverloadBuilder("resourceId") + .WithReturnType(LanguageConstants.String) + .WithGenericDescription(resourceIdDescription) + .WithRequiredParameter("subscriptionId", LanguageConstants.String, "The subscription ID") + .WithRequiredParameter("resourceGroupName", LanguageConstants.String, "The resource group name") + .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") + .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") + .Build(); + + // the subscriptionResourceId function relies on leading optional parameters that are disambiguated at runtime + // modeling this as multiple overload with all possible permutations of the leading parameters + const string subscriptionResourceIdDescription = "Returns the unique identifier for a resource deployed at the subscription level."; + yield return new FunctionOverloadBuilder("subscriptionResourceId") + .WithReturnType(LanguageConstants.String) + .WithGenericDescription(subscriptionResourceIdDescription) + .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") + .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") + .Build(); + + yield return new FunctionOverloadBuilder("subscriptionResourceId") + .WithReturnType(LanguageConstants.String) + .WithGenericDescription(subscriptionResourceIdDescription) + .WithRequiredParameter("subscriptionId", LanguageConstants.String, "The subscription ID") + .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") + .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") + .Build(); + + yield return new FunctionOverloadBuilder("tenantResourceId") + .WithReturnType(LanguageConstants.String) + .WithGenericDescription("Returns the unique identifier for a resource deployed at the tenant level.") + .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") + .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") + .Build(); + + yield return new FunctionOverloadBuilder("extensionResourceId") + .WithReturnType(LanguageConstants.String) + // .WithGenericDescription("Returns the resource ID for an extension resource, which is a resource type that is applied to another resource to add to its capabilities.") + .WithGenericDescription("Returns the resource ID for an [extension](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/extension-resource-types) resource, which is a resource type that is applied to another resource to add to its capabilities.") + .WithRequiredParameter("resourceId", LanguageConstants.String, "The resource ID for the resource that the extension resource is applied to") + .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of the extension resource including resource provider namespace") + .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The extension resource name segment") + .Build(); + + const string managementGroupResourceIdDescription = "Returns the unique identifier for a resource deployed at the management group level."; + yield return new FunctionOverloadBuilder("managementGroupResourceId") + .WithReturnType(LanguageConstants.String) + .WithGenericDescription(managementGroupResourceIdDescription) + .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") + .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") + .Build(); + + yield return new FunctionOverloadBuilder("managementGroupResourceId") + .WithReturnType(LanguageConstants.String) + .WithGenericDescription(managementGroupResourceIdDescription) + .WithRequiredParameter("managementGroupId", LanguageConstants.String, "The management group ID") + .WithRequiredParameter("resourceType", LanguageConstants.String, "Type of resource including resource provider namespace") + .WithVariableParameter("resourceName", LanguageConstants.String, minimumCount: 1, "The resource name segment") + .Build(); + + const string providersDescription = "Returns information about a resource provider and its supported resource types. If you don't provide a resource type, the function returns all the supported types for the resource provider."; + yield return new FunctionOverloadBuilder("providers") + .WithReturnResultBuilder(AddDiagnosticsAndReturnResult(GetProvidersSingleProviderReturnType(), x => x.DeprecatedProvidersFunction("providers")), GetProvidersSingleProviderReturnType()) + .WithGenericDescription(providersDescription) + .WithRequiredParameter("providerNamespace", LanguageConstants.String, "the namespace of the provider") + .Build(); + + yield return new FunctionOverloadBuilder("providers") + .WithReturnResultBuilder(AddDiagnosticsAndReturnResult(GetProvidersSingleResourceReturnType(), x => x.DeprecatedProvidersFunction("providers")), GetProvidersSingleResourceReturnType()) + .WithGenericDescription(providersDescription) + .WithRequiredParameter("providerNamespace", LanguageConstants.String, "the namespace of the provider") + .WithRequiredParameter("resourceType", LanguageConstants.String, "The type of resource within the specified namespace") + .Build(); + + // TODO: return type is string[] + // TODO: Location param should be of location type if we ever add it + yield return new FunctionOverloadBuilder("pickZones") + .WithReturnType(LanguageConstants.Array) + .WithGenericDescription("Determines whether a resource type supports zones for a region.") + .WithRequiredParameter("providerNamespace", LanguageConstants.String, "The resource provider namespace for the resource type to check for zone support") + .WithRequiredParameter("resourceType", LanguageConstants.String, "The resource type to check for zone support") + .WithRequiredParameter("location", LanguageConstants.String, "The region to check for zone support") + .WithOptionalParameter("numberOfZones", LanguageConstants.Int, "The number of logical zones to return. The default is 1. The number must a positive integer from 1 to 3. Use 1 for single-zoned resources. For multi-zoned resources, the value must be less than or equal to the number of supported zones.") + .WithOptionalParameter("offset", LanguageConstants.Int, "The offset from the starting logical zone. The function returns an error if offset plus numberOfZones exceeds the number of supported zones.") + .Build(); + + // TODO: Change 'Full' to literal type after verifying in the runtime source + yield return new FunctionOverloadBuilder("reference") + .WithReturnType(LanguageConstants.Object) + .WithGenericDescription("Returns an object representing a resource's runtime state.") + .WithRequiredParameter("resourceNameOrIdentifier", LanguageConstants.String, "Name or unique identifier of a resource. When referencing a resource in the current template, provide only the resource name as a parameter. When referencing a previously deployed resource or when the name of the resource is ambiguous, provide the resource ID.") + .WithOptionalParameter("apiVersion", LanguageConstants.String, "API version of the specified resource. This parameter is required when the resource isn't provisioned within same template.") + .WithOptionalParameter("full", LanguageConstants.String, "Value that specifies whether to return the full resource object. If you don't specify 'Full', only the properties object of the resource is returned. The full object includes values such as the resource ID and location.") + .WithFlags(FunctionFlags.RequiresInlining) + .Build(); + + // TODO: Doc parameters need an update + yield return new FunctionWildcardOverloadBuilder("list*", AzConstants.ListWildcardFunctionRegex) + .WithReturnType(LanguageConstants.Any) + .WithGenericDescription("The syntax for this function varies by name of the list operations. Each implementation returns values for the resource type that supports a list operation. The operation name must start with list. Some common usages are `listKeys`, `listKeyValue`, and `listSecrets`.") + .WithRequiredParameter("resourceNameOrIdentifier", LanguageConstants.String, "Name or unique identifier of a resource. When referencing a resource in the current template, provide only the resource name as a parameter. When referencing a previously deployed resource or when the name of the resource is ambiguous, provide the resource ID.") + .WithRequiredParameter("apiVersion", LanguageConstants.String, "API version of resource runtime state. Typically, in the format, yyyy-mm-dd.") + .WithOptionalParameter("functionValues", LanguageConstants.Object, "An object that has values for the function. Only provide this object for functions that support receiving an object with parameter values, such as listAccountSas on a storage account. An example of passing function values is shown in this article.") + .WithFlags(FunctionFlags.RequiresInlining) + .Build(); + } } - public static NamespaceType Create(string aliasName, ResourceScope resourceScope, AzResourceTypeProvider resourceTypeProvider) + public static NamespaceType Create(string aliasName, ResourceScope resourceScope, AzResourceTypeProvider resourceTypeProvider, BicepSourceFileKind bicepSourceFileKind) { return new NamespaceType( aliasName, Settings, ImmutableArray.Empty, - GetAzOverloads(resourceScope), + GetAzOverloads(resourceScope, bicepSourceFileKind), ImmutableArray.Empty, ImmutableArray.Empty, resourceTypeProvider); diff --git a/src/Bicep.Core/Semantics/Namespaces/DefaultNamespaceProvider.cs b/src/Bicep.Core/Semantics/Namespaces/DefaultNamespaceProvider.cs index 06ae388bad3..fc85a44d709 100644 --- a/src/Bicep.Core/Semantics/Namespaces/DefaultNamespaceProvider.cs +++ b/src/Bicep.Core/Semantics/Namespaces/DefaultNamespaceProvider.cs @@ -22,7 +22,7 @@ public DefaultNamespaceProvider(IAzResourceTypeLoader azResourceTypeLoader) this.providerLookup = new Dictionary { [SystemNamespaceType.BuiltInName] = (alias, scope, features, sourceFileKind) => SystemNamespaceType.Create(alias, features, sourceFileKind), - [AzNamespaceType.BuiltInName] = (alias, scope, features, sourceFileKind) => AzNamespaceType.Create(alias, scope, azResourceTypeProvider), + [AzNamespaceType.BuiltInName] = (alias, scope, features, sourceFileKind) => AzNamespaceType.Create(alias, scope, azResourceTypeProvider, sourceFileKind), [K8sNamespaceType.BuiltInName] = (alias, scope, features, sourceFileKind) => K8sNamespaceType.Create(alias), }.ToImmutableDictionary(); } diff --git a/src/Bicep.Core/Semantics/Namespaces/NamespaceResolver.cs b/src/Bicep.Core/Semantics/Namespaces/NamespaceResolver.cs index 129a9240205..7e7a2cb4e39 100644 --- a/src/Bicep.Core/Semantics/Namespaces/NamespaceResolver.cs +++ b/src/Bicep.Core/Semantics/Namespaces/NamespaceResolver.cs @@ -27,7 +27,7 @@ public static NamespaceResolver Create(IFeatureProvider features, INamespaceProv { var importedNamespaces = fileScope.Declarations.OfType() .DistinctBy(x => x.Name, LanguageConstants.IdentifierComparer); - + var builtInNamespaceSymbols = new Dictionary(LanguageConstants.IdentifierComparer); var namespaceTypes = importedNamespaces .Select(x => x.DeclaredType) @@ -61,11 +61,8 @@ void TryAddBuiltInNamespace(string @namespace) } TryAddBuiltInNamespace(SystemNamespaceType.BuiltInName); - if (sourceFile.FileKind == BicepSourceFileKind.BicepFile) - { - // don't register "az" namespace for Bicep Parameters files - TryAddBuiltInNamespace(AzNamespaceType.BuiltInName); - } + + TryAddBuiltInNamespace(AzNamespaceType.BuiltInName); return new(namespaceTypes, builtInNamespaceSymbols.ToImmutableDictionary(LanguageConstants.IdentifierComparer)); } diff --git a/src/Bicep.Core/TypeSystem/FunctionFlags.cs b/src/Bicep.Core/TypeSystem/FunctionFlags.cs index 52d7a1764bc..f5c1d211002 100644 --- a/src/Bicep.Core/TypeSystem/FunctionFlags.cs +++ b/src/Bicep.Core/TypeSystem/FunctionFlags.cs @@ -76,6 +76,11 @@ public enum FunctionFlags /// FunctionDecorator = 1 << 13, + /// + /// The function can be used in direct assignment only + /// + DirectAssignment = 1 << 14, + /// /// The function can be used as a resource or module decorator. ///