private JToken GenerateTemplateWithoutHash() { // TODO: since we merely return a JToken, refactor the emitter logic to add properties to a JObject // instead of writing to a JsonWriter and converting it to JToken at the end using var stringWriter = new StringWriter(); using var jsonWriter = new JsonTextWriter(stringWriter); var emitter = new ExpressionEmitter(jsonWriter, this.context); jsonWriter.WriteStartObject(); emitter.EmitProperty("$schema", GetSchema(context.SemanticModel.TargetScope)); emitter.EmitProperty("contentVersion", "1.0.0.0"); this.EmitMetadata(jsonWriter, emitter); this.EmitParametersIfPresent(jsonWriter, emitter); jsonWriter.WritePropertyName("functions"); jsonWriter.WriteStartArray(); jsonWriter.WriteEndArray(); this.EmitVariablesIfPresent(jsonWriter, emitter); this.EmitResources(jsonWriter, emitter); this.EmitOutputsIfPresent(jsonWriter, emitter); jsonWriter.WriteEndObject(); return(stringWriter.ToString().FromJson <JToken>()); }
public static void EmitModuleScopeProperties(ScopeData scopeData, ExpressionEmitter expressionEmitter) { switch (scopeData.RequestedScope) { case ResourceScopeType.TenantScope: expressionEmitter.EmitProperty("scope", new JTokenExpression("/")); return; case ResourceScopeType.ManagementGroupScope: if (scopeData.ManagementGroupNameProperty != null) { expressionEmitter.EmitProperty("scope", () => expressionEmitter.EmitManagementGroupScope(scopeData.ManagementGroupNameProperty)); } return; case ResourceScopeType.SubscriptionScope: case ResourceScopeType.ResourceGroupScope: if (scopeData.SubscriptionIdProperty != null) { expressionEmitter.EmitProperty("subscriptionId", scopeData.SubscriptionIdProperty); } if (scopeData.ResourceGroupProperty != null) { expressionEmitter.EmitProperty("resourceGroup", scopeData.ResourceGroupProperty); } return; default: throw new NotImplementedException($"Cannot format resourceId for scope {scopeData.RequestedScope}"); } }
private void EmitImports(JsonTextWriter jsonWriter, ExpressionEmitter emitter) { if (!context.SemanticModel.Root.ImportDeclarations.Any()) { return; } jsonWriter.WritePropertyName("imports"); jsonWriter.WriteStartObject(); foreach (var import in this.context.SemanticModel.Root.ImportDeclarations) { var namespaceType = context.SemanticModel.GetTypeInfo(import.DeclaringSyntax) as NamespaceType ?? throw new ArgumentException("Imported namespace does not have namespace type"); jsonWriter.WritePropertyName(import.DeclaringImport.AliasName.IdentifierName); jsonWriter.WriteStartObject(); emitter.EmitProperty("provider", namespaceType.Settings.ArmTemplateProviderName); emitter.EmitProperty("version", namespaceType.Settings.ArmTemplateProviderVersion); if (import.DeclaringImport.Config is { } config) { emitter.EmitProperty("config", config); } jsonWriter.WriteEndObject(); } jsonWriter.WriteEndObject(); }
private JToken GenerateTemplate(string contentVersion) { using var stringWriter = new StringWriter(); using var jsonWriter = new JsonTextWriter(stringWriter); var emitter = new ExpressionEmitter(jsonWriter, this.context); jsonWriter.WriteStartObject(); emitter.EmitProperty("$schema", "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#"); emitter.EmitProperty("contentVersion", contentVersion); if (this.context.SemanticModel.Root.ParameterDeclarations.Length > 0) { jsonWriter.WritePropertyName("parameters"); jsonWriter.WriteStartObject(); foreach (var parameterSymbol in this.context.SemanticModel.Root.ParameterDeclarations) { if (parameterSymbol.DeclaringParameter.Modifier is not ParameterDefaultValueSyntax) { jsonWriter.WritePropertyName(parameterSymbol.Name); jsonWriter.WriteStartObject(); switch (parameterSymbol.Type.Name) { case "string": emitter.EmitProperty("value", ""); break; case "int": emitter.EmitProperty("value", () => jsonWriter.WriteValue(0)); break; case "bool": emitter.EmitProperty("value", () => jsonWriter.WriteValue(false)); break; case "object": emitter.EmitProperty("value", () => { jsonWriter.WriteStartObject(); jsonWriter.WriteEndObject(); }); break; case "array": emitter.EmitProperty("value", () => { jsonWriter.WriteStartArray(); jsonWriter.WriteEndArray(); }); break; } jsonWriter.WriteEndObject(); } } jsonWriter.WriteEndObject(); } jsonWriter.WriteEndObject(); var content = stringWriter.ToString(); return(content.FromJson <JToken>()); }
private void EmitVariablesIfPresent(JsonTextWriter jsonWriter, ExpressionEmitter emitter) { if (!this.context.SemanticModel.Root.VariableDeclarations.Any(symbol => !this.context.VariablesToInline.Contains(symbol)) && this.context.FunctionVariables.Count == 0) { return; } jsonWriter.WritePropertyName("variables"); jsonWriter.WriteStartObject(); //emit internal variables foreach (var functionVariable in this.context.FunctionVariables.Values.OrderBy(x => x.Name, LanguageConstants.IdentifierComparer)) { jsonWriter.WritePropertyName(functionVariable.Name); emitter.EmitExpression(functionVariable.Value); } var variableLookup = this.context.SemanticModel.Root.VariableDeclarations.ToLookup(variableSymbol => variableSymbol.Value is ForSyntax); // local function IEnumerable <VariableSymbol> GetNonInlinedVariables(bool valueIsLoop) => variableLookup[valueIsLoop].Where(symbol => !this.context.VariablesToInline.Contains(symbol)); if (GetNonInlinedVariables(valueIsLoop: true).Any()) { // we have variables whose values are loops emitter.EmitProperty("copy", () => { jsonWriter.WriteStartArray(); foreach (var variableSymbol in GetNonInlinedVariables(valueIsLoop: true)) { // enforced by the lookup predicate above var @for = (ForSyntax)variableSymbol.Value; emitter.EmitCopyObject(variableSymbol.Name, @for, @for.Body); } jsonWriter.WriteEndArray(); }); } // emit non-loop variables foreach (var variableSymbol in GetNonInlinedVariables(valueIsLoop: false)) { jsonWriter.WritePropertyName(variableSymbol.Name); emitter.EmitExpression(variableSymbol.Value); } jsonWriter.WriteEndObject(); }
private void EmitResources(JsonTextWriter jsonWriter, ExpressionEmitter emitter) { jsonWriter.WritePropertyName("resources"); if (context.Settings.EnableSymbolicNames) { jsonWriter.WriteStartObject(); } else { jsonWriter.WriteStartArray(); } foreach (var resource in this.context.SemanticModel.AllResources) { if (resource.IsExistingResource && !context.Settings.EnableSymbolicNames) { continue; } if (context.Settings.EnableSymbolicNames) { jsonWriter.WritePropertyName(resource.Symbol.Name); } this.EmitResource(jsonWriter, resource, emitter); } foreach (var moduleSymbol in this.context.SemanticModel.Root.ModuleDeclarations) { if (context.Settings.EnableSymbolicNames) { jsonWriter.WritePropertyName(moduleSymbol.Name); } this.EmitModule(jsonWriter, moduleSymbol, emitter); } if (context.Settings.EnableSymbolicNames) { jsonWriter.WriteEndObject(); } else { jsonWriter.WriteEndArray(); } }
/// <summary> /// This emits the template into memory, calculates the template hash of the template /// It then adds the templateHash field to the template and finally writes it to our desired JsonTextWriter /// </summary> public void Write() { string templateString; using (StringWriter stringWriter = new()) using (JsonTextWriter memoryWriter = new(stringWriter)) { var emitter = new ExpressionEmitter(memoryWriter, this.context); // no templatehash, write to memory this.Write(memoryWriter, emitter, emitMetadata: true); // TODO: avoid reading the whole template into a string. Address this when ComputeTemplateHash // has a streaming variant. templateString = stringWriter.ToString(); } var templateHash = TemplateHashExtensions.ComputeTemplateHash(templateString); JObject template = (JObject)JObject.Parse(templateString); ((JObject)template["metadata"] !["_generator"] !).Add(new JProperty("templateHash", templateHash));
private void EmitParametersIfPresent(JsonTextWriter jsonWriter, ExpressionEmitter emitter) { if (this.context.SemanticModel.Root.ParameterDeclarations.Length == 0) { return; } jsonWriter.WritePropertyName("parameters"); jsonWriter.WriteStartObject(); foreach (var parameterSymbol in this.context.SemanticModel.Root.ParameterDeclarations) { jsonWriter.WritePropertyName(parameterSymbol.Name); this.EmitParameter(jsonWriter, parameterSymbol, emitter); } jsonWriter.WriteEndObject(); }
private void EmitVariablesIfPresent(JsonTextWriter jsonWriter, ExpressionEmitter emitter) { if (!this.context.SemanticModel.Root.VariableDeclarations.Any(symbol => !this.context.VariablesToInline.Contains(symbol))) { return; } jsonWriter.WritePropertyName("variables"); jsonWriter.WriteStartObject(); foreach (var variableSymbol in this.context.SemanticModel.Root.VariableDeclarations) { if (!this.context.VariablesToInline.Contains(variableSymbol)) { jsonWriter.WritePropertyName(variableSymbol.Name); this.EmitVariable(variableSymbol, emitter); } } jsonWriter.WriteEndObject(); }
private void EmitResources(JsonTextWriter jsonWriter, ExpressionEmitter emitter) { jsonWriter.WritePropertyName("resources"); jsonWriter.WriteStartArray(); foreach (var resourceSymbol in this.context.SemanticModel.Root.GetAllResourceDeclarations()) { if (resourceSymbol.DeclaringResource.IsExistingResource()) { continue; } this.EmitResource(jsonWriter, resourceSymbol, emitter); } foreach (var moduleSymbol in this.context.SemanticModel.Root.ModuleDeclarations) { this.EmitModule(jsonWriter, moduleSymbol, emitter); } jsonWriter.WriteEndArray(); }
private (Template, JToken) GenerateTemplateWithoutHash() { // TODO: since we merely return a JToken, refactor the emitter logic to add properties to a JObject // instead of writing to a JsonWriter and converting it to JToken at the end using var stringWriter = new StringWriter(); using var jsonWriter = new JsonTextWriter(stringWriter); var emitter = new ExpressionEmitter(jsonWriter, this.context); jsonWriter.WriteStartObject(); emitter.EmitProperty("$schema", GetSchema(context.SemanticModel.TargetScope)); if (context.Settings.EnableSymbolicNames) { emitter.EmitProperty("languageVersion", "1.9-experimental"); } emitter.EmitProperty("contentVersion", "1.0.0.0"); this.EmitMetadata(jsonWriter, emitter); this.EmitParametersIfPresent(jsonWriter, emitter); this.EmitVariablesIfPresent(jsonWriter, emitter); this.EmitImports(jsonWriter, emitter); this.EmitResources(jsonWriter, emitter); this.EmitOutputsIfPresent(jsonWriter, emitter); jsonWriter.WriteEndObject(); var content = stringWriter.ToString(); return(Template.FromJson <Template>(content), content.FromJson <JToken>()); }
private void EmitResource(JsonTextWriter jsonWriter, ResourceMetadata resource, ExpressionEmitter emitter) { jsonWriter.WriteStartObject(); // Note: conditions STACK with nesting. // // Children inherit the conditions of their parents, etc. This avoids a problem // where we emit a dependsOn to something that's not in the template, or not // being evaulated i the template. var conditions = new List <SyntaxBase>(); var loops = new List <(string name, ForSyntax @for, SyntaxBase?input)>(); var ancestors = this.context.SemanticModel.ResourceAncestors.GetAncestors(resource); foreach (var ancestor in ancestors) { if (ancestor.AncestorType == ResourceAncestorGraph.ResourceAncestorType.Nested && ancestor.Resource.Symbol.DeclaringResource.Value is IfConditionSyntax ifCondition) { conditions.Add(ifCondition.ConditionExpression); } if (ancestor.AncestorType == ResourceAncestorGraph.ResourceAncestorType.Nested && ancestor.Resource.Symbol.DeclaringResource.Value is ForSyntax @for) { loops.Add((ancestor.Resource.Symbol.Name, @for, null)); } } // Unwrap the 'real' resource body if there's a condition var body = resource.Symbol.DeclaringResource.Value; switch (body) { case IfConditionSyntax ifCondition: body = ifCondition.Body; conditions.Add(ifCondition.ConditionExpression); break; case ForSyntax @for: loops.Add((resource.Symbol.Name, @for, null)); if (@for.Body is IfConditionSyntax loopFilter) { body = loopFilter.Body; conditions.Add(loopFilter.ConditionExpression); } else { body = @for.Body; } break; } if (conditions.Count == 1) { emitter.EmitProperty("condition", conditions[0]); } else if (conditions.Count > 1) { var @operator = new BinaryOperationSyntax( conditions[0], SyntaxFactory.CreateToken(TokenType.LogicalAnd), conditions[1]); for (var i = 2; i < conditions.Count; i++) { @operator = new BinaryOperationSyntax( @operator, SyntaxFactory.CreateToken(TokenType.LogicalAnd), conditions[i]); } emitter.EmitProperty("condition", @operator); } if (loops.Count == 1) { var batchSize = GetBatchSize(resource.Symbol.DeclaringResource); emitter.EmitProperty("copy", () => emitter.EmitCopyObject(loops[0].name, loops[0].@for, loops[0].input, batchSize: batchSize)); } else if (loops.Count > 1) { throw new InvalidOperationException("nested loops are not supported"); } if (context.Settings.EnableSymbolicNames && resource.IsExistingResource) { jsonWriter.WritePropertyName("existing"); jsonWriter.WriteValue(true); } var importSymbol = context.SemanticModel.Root.ImportDeclarations.FirstOrDefault(i => resource.Type.DeclaringNamespace.AliasNameEquals(i.Name)); if (importSymbol is not null) { emitter.EmitProperty("import", importSymbol.Name); } if (resource.IsAzResource) { emitter.EmitProperty("type", resource.TypeReference.FormatType()); if (resource.TypeReference.ApiVersion is not null) { emitter.EmitProperty("apiVersion", resource.TypeReference.ApiVersion); } } else { emitter.EmitProperty("type", resource.TypeReference.FormatName()); } if (context.SemanticModel.EmitLimitationInfo.ResourceScopeData.TryGetValue(resource, out var scopeData)) { ScopeHelper.EmitResourceScopeProperties(context.SemanticModel, scopeData, emitter, body); } if (resource.IsAzResource) { emitter.EmitProperty(AzResourceTypeProvider.ResourceNamePropertyName, emitter.GetFullyQualifiedResourceName(resource)); emitter.EmitObjectProperties((ObjectSyntax)body, ResourcePropertiesToOmit.Add(AzResourceTypeProvider.ResourceNamePropertyName)); } else { jsonWriter.WritePropertyName("properties"); jsonWriter.WriteStartObject(); emitter.EmitObjectProperties((ObjectSyntax)body, ResourcePropertiesToOmit); jsonWriter.WriteEndObject(); } this.EmitDependsOn(jsonWriter, resource.Symbol, emitter, body); // Since we don't want to be mutating the body of the original ObjectSyntax, we create an placeholder body in place // and emit its properties to merge decorator properties. foreach (var(property, val) in AddDecoratorsToBody( resource.Symbol.DeclaringResource, SyntaxFactory.CreateObject(Enumerable.Empty <ObjectPropertySyntax>()), resource.Symbol.Type).ToNamedPropertyValueDictionary()) { emitter.EmitProperty(property, val); } jsonWriter.WriteEndObject(); }
private void EmitParameter(JsonTextWriter jsonWriter, ParameterSymbol parameterSymbol, ExpressionEmitter emitter) { var declaringParameter = parameterSymbol.DeclaringParameter; if (SyntaxHelper.TryGetPrimitiveType(declaringParameter) is not TypeSymbol primitiveType) { // this should have been caught by the type checker long ago throw new ArgumentException($"Unable to find primitive type for parameter {parameterSymbol.Name}"); } jsonWriter.WriteStartObject(); var parameterType = SyntaxFactory.CreateStringLiteral(primitiveType.Name); var parameterObject = SyntaxFactory.CreateObject(SyntaxFactory.CreateObjectProperty("type", parameterType).AsEnumerable()); if (declaringParameter.Modifier is ParameterDefaultValueSyntax defaultValueSyntax) { parameterObject = parameterObject.MergeProperty("defaultValue", defaultValueSyntax.DefaultValue); } parameterObject = EvaluateDecorators(declaringParameter, parameterObject, primitiveType); foreach (var property in parameterObject.Properties) { if (property.TryGetKeyText() is string propertyName) { emitter.EmitProperty(propertyName, property.Value); } } jsonWriter.WriteEndObject(); }
private void EmitParameter(JsonTextWriter jsonWriter, ParameterSymbol parameterSymbol, ExpressionEmitter emitter) {
private void EmitModuleParameters(JsonTextWriter jsonWriter, ModuleSymbol moduleSymbol, ExpressionEmitter emitter) { var paramsValue = moduleSymbol.TryGetBodyPropertyValue(LanguageConstants.ModuleParamsPropertyName); if (paramsValue is not ObjectSyntax paramsObjectSyntax) { // 'params' is optional if the module has no required params return; } jsonWriter.WritePropertyName("parameters"); jsonWriter.WriteStartObject(); foreach (var propertySyntax in paramsObjectSyntax.Properties) { if (!(propertySyntax.TryGetKeyText() is string keyName)) { // should have been caught by earlier validation throw new ArgumentException("Disallowed interpolation in module parameter"); } // we can't just call EmitObjectProperties here because the ObjectSyntax is flatter than the structure we're generating // because nested deployment parameters are objects with a single value property jsonWriter.WritePropertyName(keyName); jsonWriter.WriteStartObject(); if (propertySyntax.Value is ForSyntax @for) { // the value is a for-expression // write a single property copy loop emitter.EmitProperty("copy", () => { jsonWriter.WriteStartArray(); emitter.EmitCopyObject("value", @for, @for.Body, "value"); jsonWriter.WriteEndArray(); }); } else if (this.context.SemanticModel.ResourceMetadata.TryLookup(propertySyntax.Value) is {} resourceMetadata) { // This is a resource being passed into a module, we actually want to pass in its id // rather than the whole resource. emitter.EmitProperty("value", new PropertyAccessSyntax(propertySyntax.Value, SyntaxFactory.DotToken, SyntaxFactory.CreateIdentifier("id"))); }
private void EmitParameter(JsonTextWriter jsonWriter, ParameterSymbol parameterSymbol, ExpressionEmitter emitter) { var declaringParameter = parameterSymbol.DeclaringParameter; var properties = new List <ObjectPropertySyntax>(); if (parameterSymbol.Type is ResourceType resourceType) { // Encode a resource type as a string parameter with a metadata for the resource type. properties.Add(SyntaxFactory.CreateObjectProperty("type", SyntaxFactory.CreateStringLiteral(LanguageConstants.String.Name))); properties.Add(SyntaxFactory.CreateObjectProperty( LanguageConstants.ParameterMetadataPropertyName, SyntaxFactory.CreateObject(new[] { SyntaxFactory.CreateObjectProperty( LanguageConstants.MetadataResourceTypePropertyName, SyntaxFactory.CreateStringLiteral(resourceType.TypeReference.FormatName())), }))); } else if (SyntaxHelper.TryGetPrimitiveType(declaringParameter) is TypeSymbol primitiveType) { properties.Add(SyntaxFactory.CreateObjectProperty("type", SyntaxFactory.CreateStringLiteral(primitiveType.Name))); } else { // this should have been caught by the type checker long ago throw new ArgumentException($"Unable to find primitive type for parameter {parameterSymbol.Name}"); } jsonWriter.WriteStartObject(); var parameterObject = SyntaxFactory.CreateObject(properties); if (declaringParameter.Modifier is ParameterDefaultValueSyntax defaultValueSyntax) { parameterObject = parameterObject.MergeProperty("defaultValue", defaultValueSyntax.DefaultValue); } parameterObject = AddDecoratorsToBody(declaringParameter, parameterObject, SyntaxHelper.TryGetPrimitiveType(declaringParameter) ?? parameterSymbol.Type); foreach (var property in parameterObject.Properties) { if (property.TryGetKeyText() is string propertyName) { emitter.EmitProperty(propertyName, property.Value); } } jsonWriter.WriteEndObject(); }
public static void EmitModuleScopeProperties(ResourceScopeType targetScope, ScopeData scopeData, ExpressionEmitter expressionEmitter) { switch (scopeData.RequestedScope) { case ResourceScopeType.TenantScope: expressionEmitter.EmitProperty("scope", new JTokenExpression("/")); return; case ResourceScopeType.ManagementGroupScope: if (scopeData.ManagementGroupNameProperty != null) { // The template engine expects an unqualified resourceId for the management group scope if deploying at tenant scope var useFullyQualifiedResourceId = targetScope != ResourceScopeType.TenantScope; expressionEmitter.EmitProperty("scope", expressionEmitter.GetManagementGroupResourceId(scopeData.ManagementGroupNameProperty, useFullyQualifiedResourceId)); } return; case ResourceScopeType.SubscriptionScope: case ResourceScopeType.ResourceGroupScope: if (scopeData.SubscriptionIdProperty != null) { expressionEmitter.EmitProperty("subscriptionId", scopeData.SubscriptionIdProperty); } if (scopeData.ResourceGroupProperty != null) { expressionEmitter.EmitProperty("resourceGroup", scopeData.ResourceGroupProperty); } return; default: throw new NotImplementedException($"Cannot format resourceId for scope {scopeData.RequestedScope}"); } }
private void EmitParameter(JsonTextWriter jsonWriter, ParameterSymbol parameterSymbol, ExpressionEmitter emitter) { // local function bool IsSecure(SyntaxBase?value) => value is BooleanLiteralSyntax boolLiteral && boolLiteral.Value; if (!(SyntaxHelper.TryGetPrimitiveType(parameterSymbol.DeclaringParameter) is TypeSymbol primitiveType)) { // this should have been caught by the type checker long ago throw new ArgumentException($"Unable to find primitive type for parameter {parameterSymbol.Name}"); } jsonWriter.WriteStartObject(); if (parameterSymbol.DeclaringParameter.Decorators.Any()) { var parameterType = SyntaxFactory.CreateStringLiteral(primitiveType.Name); var parameterObject = SyntaxFactory.CreateObject(SyntaxFactory.CreateObjectProperty("type", parameterType).AsEnumerable()); if (parameterSymbol.Modifier is ParameterDefaultValueSyntax defaultValueSyntax) { parameterObject = parameterObject.MergeProperty("defaultValue", defaultValueSyntax.DefaultValue); } parameterObject = EvaluateDecorators(parameterSymbol.DeclaringParameter, parameterObject, primitiveType); foreach (var property in parameterObject.Properties) { if (property.TryGetKeyText() is string propertyName) { emitter.EmitProperty(propertyName, property.Value); } } } else { // TODO: remove this before the 0.3 release. switch (parameterSymbol.Modifier) { case null: emitter.EmitProperty("type", GetTemplateTypeName(primitiveType, secure: false)); break; case ParameterDefaultValueSyntax defaultValueSyntax: emitter.EmitProperty("type", GetTemplateTypeName(primitiveType, secure: false)); emitter.EmitProperty("defaultValue", defaultValueSyntax.DefaultValue); break; case ObjectSyntax modifierSyntax: // this would throw on duplicate properties in the object node - we are relying on emitter checking for errors at the beginning var properties = modifierSyntax.ToKnownPropertyValueDictionary(); emitter.EmitProperty("type", GetTemplateTypeName(primitiveType, IsSecure(properties.TryGetValue("secure")))); // relying on validation here as well (not all of the properties are valid in all contexts) foreach (string modifierPropertyName in ParameterModifierPropertiesToEmitDirectly) { emitter.EmitOptionalPropertyExpression(modifierPropertyName, properties.TryGetValue(modifierPropertyName)); } emitter.EmitOptionalPropertyExpression("defaultValue", properties.TryGetValue(LanguageConstants.ParameterDefaultPropertyName)); emitter.EmitOptionalPropertyExpression("allowedValues", properties.TryGetValue(LanguageConstants.ParameterAllowedPropertyName)); break; } } jsonWriter.WriteEndObject(); }
private static void EmitModuleParameters(JsonTextWriter jsonWriter, ModuleSymbol moduleSymbol, ExpressionEmitter emitter) { var paramsValue = moduleSymbol.TryGetBodyPropertyValue(LanguageConstants.ModuleParamsPropertyName); if (paramsValue is not ObjectSyntax paramsObjectSyntax) { // 'params' is optional if the module has no required params return; } jsonWriter.WritePropertyName("parameters"); jsonWriter.WriteStartObject(); foreach (var propertySyntax in paramsObjectSyntax.Properties) { if (!(propertySyntax.TryGetKeyText() is string keyName)) { // should have been caught by earlier validation throw new ArgumentException("Disallowed interpolation in module parameter"); } // we can't just call EmitObjectProperties here because the ObjectSyntax is flatter than the structure we're generating // because nested deployment parameters are objects with a single value property jsonWriter.WritePropertyName(keyName); jsonWriter.WriteStartObject(); if (propertySyntax.Value is ForSyntax @for) { // the value is a for-expression // write a single property copy loop emitter.EmitProperty("copy", () => { jsonWriter.WriteStartArray(); emitter.EmitCopyObject("value", @for, @for.Body, "value"); jsonWriter.WriteEndArray(); }); } else { // the value is not a for-expression - can emit normally emitter.EmitModuleParameterValue(propertySyntax.Value); } jsonWriter.WriteEndObject(); } jsonWriter.WriteEndObject(); }
private void EmitVariable(VariableSymbol variableSymbol, ExpressionEmitter emitter) { emitter.EmitExpression(variableSymbol.Value); }
private void EmitModule(JsonTextWriter jsonWriter, ModuleSymbol moduleSymbol, ExpressionEmitter emitter) { jsonWriter.WriteStartObject(); var body = moduleSymbol.DeclaringModule.Value; switch (body) { case IfConditionSyntax ifCondition: body = ifCondition.Body; emitter.EmitProperty("condition", ifCondition.ConditionExpression); break; case ForSyntax @for: if (@for.Body is IfConditionSyntax loopFilter) { body = loopFilter.Body; emitter.EmitProperty("condition", loopFilter.ConditionExpression); } else { body = @for.Body; } var batchSize = GetBatchSize(moduleSymbol.DeclaringModule); emitter.EmitProperty("copy", () => emitter.EmitCopyObject(moduleSymbol.Name, @for, input: null, batchSize: batchSize)); break; } emitter.EmitProperty("type", NestedDeploymentResourceType); emitter.EmitProperty("apiVersion", NestedDeploymentResourceApiVersion); // emit all properties apart from 'params'. In practice, this currrently only allows 'name', but we may choose to allow other top-level resource properties in future. // params requires special handling (see below). emitter.EmitObjectProperties((ObjectSyntax)body, ModulePropertiesToOmit); var scopeData = context.ModuleScopeData[moduleSymbol]; ScopeHelper.EmitModuleScopeProperties(context.SemanticModel.TargetScope, scopeData, emitter, body); if (scopeData.RequestedScope != ResourceScope.ResourceGroup) { // if we're deploying to a scope other than resource group, we need to supply a location if (this.context.SemanticModel.TargetScope == ResourceScope.ResourceGroup) { // the deployment() object at resource group scope does not contain a property named 'location', so we have to use resourceGroup().location emitter.EmitProperty("location", new FunctionExpression( "resourceGroup", Array.Empty <LanguageExpression>(), new LanguageExpression[] { new JTokenExpression("location") })); } else { // at all other scopes we can just use deployment().location emitter.EmitProperty("location", new FunctionExpression( "deployment", Array.Empty <LanguageExpression>(), new LanguageExpression[] { new JTokenExpression("location") })); } } jsonWriter.WritePropertyName("properties"); { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("expressionEvaluationOptions"); { jsonWriter.WriteStartObject(); emitter.EmitProperty("scope", "inner"); jsonWriter.WriteEndObject(); } emitter.EmitProperty("mode", "Incremental"); EmitModuleParameters(jsonWriter, moduleSymbol, emitter); var moduleSemanticModel = GetModuleSemanticModel(moduleSymbol); // If it is a template spec module, emit templateLink instead of template contents. jsonWriter.WritePropertyName(moduleSemanticModel is TemplateSpecSemanticModel ? "templateLink" : "template"); { TemplateWriterFactory.CreateTemplateWriter(moduleSemanticModel, this.settings).Write(jsonWriter); } jsonWriter.WriteEndObject(); } this.EmitDependsOn(jsonWriter, moduleSymbol, emitter, body); // Since we don't want to be mutating the body of the original ObjectSyntax, we create an placeholder body in place // and emit its properties to merge decorator properties. foreach (var(property, val) in AddDecoratorsToBody( moduleSymbol.DeclaringModule, SyntaxFactory.CreateObject(Enumerable.Empty <ObjectPropertySyntax>()), moduleSymbol.Type).ToNamedPropertyValueDictionary()) { emitter.EmitProperty(property, val); } jsonWriter.WriteEndObject(); }
public TemplateWriter(JsonTextWriter writer, SemanticModel semanticModel) { this.writer = writer; this.context = new EmitterContext(semanticModel); this.emitter = new ExpressionEmitter(writer, context); }
private void EmitResource(JsonTextWriter jsonWriter, ResourceMetadata resource, ExpressionEmitter emitter) { jsonWriter.WriteStartObject(); // Note: conditions STACK with nesting. // // Children inherit the conditions of their parents, etc. This avoids a problem // where we emit a dependsOn to something that's not in the template, or not // being evaulated i the template. var conditions = new List <SyntaxBase>(); var loops = new List <(string name, ForSyntax @for, SyntaxBase?input)>(); var ancestors = this.context.SemanticModel.ResourceAncestors.GetAncestors(resource); foreach (var ancestor in ancestors) { if (ancestor.AncestorType == ResourceAncestorGraph.ResourceAncestorType.Nested && ancestor.Resource.Symbol.DeclaringResource.Value is IfConditionSyntax ifCondition) { conditions.Add(ifCondition.ConditionExpression); } if (ancestor.AncestorType == ResourceAncestorGraph.ResourceAncestorType.Nested && ancestor.Resource.Symbol.DeclaringResource.Value is ForSyntax @for) { loops.Add((ancestor.Resource.Symbol.Name, @for, null)); } } // Unwrap the 'real' resource body if there's a condition var body = resource.Symbol.DeclaringResource.Value; switch (body) { case IfConditionSyntax ifCondition: body = ifCondition.Body; conditions.Add(ifCondition.ConditionExpression); break; case ForSyntax @for: loops.Add((resource.Symbol.Name, @for, null)); if (@for.Body is IfConditionSyntax loopFilter) { body = loopFilter.Body; conditions.Add(loopFilter.ConditionExpression); } else { body = @for.Body; } break; } if (conditions.Count == 1) { emitter.EmitProperty("condition", conditions[0]); } else if (conditions.Count > 1) { var @operator = new BinaryOperationSyntax( conditions[0], SyntaxFactory.CreateToken(TokenType.LogicalAnd), conditions[1]); for (var i = 2; i < conditions.Count; i++) { @operator = new BinaryOperationSyntax( @operator, SyntaxFactory.CreateToken(TokenType.LogicalAnd), conditions[i]); } emitter.EmitProperty("condition", @operator); } if (loops.Count == 1) { var batchSize = GetBatchSize(resource.Symbol.DeclaringResource); emitter.EmitProperty("copy", () => emitter.EmitCopyObject(loops[0].name, loops[0].@for, loops[0].input, batchSize: batchSize)); } else if (loops.Count > 1) { throw new InvalidOperationException("nested loops are not supported"); } emitter.EmitProperty("type", resource.TypeReference.FullyQualifiedType); emitter.EmitProperty("apiVersion", resource.TypeReference.ApiVersion); if (context.SemanticModel.EmitLimitationInfo.ResourceScopeData.TryGetValue(resource, out var scopeData)) { ScopeHelper.EmitResourceScopeProperties(context.SemanticModel, scopeData, emitter, body); } emitter.EmitProperty("name", emitter.GetFullyQualifiedResourceName(resource)); if (context.Settings.EnableSymbolicNames && resource.IsExistingResource) { jsonWriter.WritePropertyName("existing"); jsonWriter.WriteValue(true); } body = AddDecoratorsToBody(resource.Symbol.DeclaringResource, (ObjectSyntax)body, resource.Type); emitter.EmitObjectProperties((ObjectSyntax)body, ResourcePropertiesToOmit); this.EmitDependsOn(jsonWriter, resource.Symbol, emitter, body); jsonWriter.WriteEndObject(); }