public LanguageExpression GetModuleResourceIdExpression(ModuleSymbol moduleSymbol) { return(ScopeHelper.FormatCrossScopeResourceId( this, context.ModuleScopeData[moduleSymbol], TemplateWriter.NestedDeploymentResourceType, GetModuleNameExpression(moduleSymbol).AsEnumerable())); }
public LanguageExpression GetFullyQualifiedResourceId(ResourceMetadata resource) { return(ScopeHelper.FormatFullyQualifiedResourceId( context, this, context.ResourceScopeData[resource], resource.TypeReference.FormatType(), GetResourceNameSegments(resource))); }
public LanguageExpression GetFullyQualifiedResourceId(ResourceSymbol resourceSymbol) { var typeReference = EmitHelpers.GetTypeReference(resourceSymbol); return(ScopeHelper.FormatFullyQualifiedResourceId( context, this, context.ResourceScopeData[resourceSymbol], typeReference.FullyQualifiedType, GetResourceNameSegments(resourceSymbol, typeReference))); }
private LanguageExpression GenerateScopedResourceId(ResourceSymbol resourceSymbol, ResourceScope?targetScope) { var typeReference = EmitHelpers.GetTypeReference(resourceSymbol); var nameSegments = GetResourceNameSegments(resourceSymbol, typeReference); if (!context.ResourceScopeData.TryGetValue(resourceSymbol, out var scopeData)) { return(ScopeHelper.FormatLocallyScopedResourceId(targetScope, typeReference.FullyQualifiedType, nameSegments)); } return(ScopeHelper.FormatCrossScopeResourceId(this, scopeData, typeReference.FullyQualifiedType, nameSegments)); }
public static EmitLimitationInfo Calculate(SemanticModel model) { var diagnosticWriter = ToListDiagnosticWriter.Create(); var moduleScopeData = ScopeHelper.GetModuleScopeInfo(model, diagnosticWriter); var resourceScopeData = ScopeHelper.GetResoureScopeInfo(model, diagnosticWriter); DeployTimeConstantVisitor.ValidateDeployTimeConstants(model, diagnosticWriter); diagnosticWriter.WriteMultiple(DetectDuplicateNames(model, resourceScopeData, moduleScopeData)); return(new EmitLimitationInfo(diagnosticWriter.GetDiagnostics(), moduleScopeData, resourceScopeData)); }
public static ImmutableDictionary <ModuleSymbol, ScopeHelper.ScopeData> GetSupportedScopeInfo(SemanticModel semanticModel, IDiagnosticWriter diagnosticWriter) { var moduleScopeData = new Dictionary <ModuleSymbol, ScopeHelper.ScopeData>(); foreach (var moduleSymbol in semanticModel.Root.ModuleDeclarations) { var scopeValue = moduleSymbol.SafeGetBodyPropertyValue(LanguageConstants.ResourceScopePropertyName); if (scopeValue == null) { // no scope provided - assume the parent scope moduleScopeData[moduleSymbol] = new ScopeHelper.ScopeData { RequestedScope = semanticModel.TargetScope }; continue; } var scopeType = semanticModel.GetTypeInfo(scopeValue); var scopeData = ScopeHelper.TryGetScopeData(semanticModel.TargetScope, scopeType); if (scopeData != null) { moduleScopeData[moduleSymbol] = scopeData; continue; } switch (semanticModel.TargetScope) { case ResourceScopeType.TenantScope: diagnosticWriter.Write(scopeValue, x => x.InvalidModuleScopeForTenantScope()); break; case ResourceScopeType.ManagementGroupScope: diagnosticWriter.Write(scopeValue, x => x.InvalidModuleScopeForManagementScope()); break; case ResourceScopeType.SubscriptionScope: diagnosticWriter.Write(scopeValue, x => x.InvalidModuleScopeForSubscriptionScope()); break; case ResourceScopeType.ResourceGroupScope: diagnosticWriter.Write(scopeValue, x => x.InvalidModuleScopeForResourceGroup()); break; default: throw new InvalidOperationException($"Unrecognized target scope {semanticModel.TargetScope}"); } } return(moduleScopeData.ToImmutableDictionary()); }
private LanguageExpression GenerateScopedResourceId(ResourceSymbol resourceSymbol, ResourceScopeType?targetScope) { var typeReference = EmitHelpers.GetTypeReference(resourceSymbol); var nameSegments = GetResourceNameSegments(resourceSymbol, typeReference); if (context.ResoureScopeData[resourceSymbol] is {} parentResourceSymbol) { // this should be safe because we've already checked for cycles by now var parentResourceId = GetUnqualifiedResourceId(parentResourceSymbol); return(ExpressionConverter.GenerateScopedResourceId(parentResourceId, typeReference.FullyQualifiedType, nameSegments)); } return(ScopeHelper.FormatLocallyScopedResourceId(targetScope, typeReference.FullyQualifiedType, nameSegments)); }
private void EmitModule(ModuleSymbol moduleSymbol) { writer.WriteStartObject(); this.emitter.EmitProperty("type", NestedDeploymentResourceType); this.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). var moduleBody = (ObjectSyntax)moduleSymbol.DeclaringModule.Body; this.emitter.EmitObjectProperties(moduleBody, ModulePropertiesToOmit); var scopeData = context.ModuleScopeData[moduleSymbol]; ScopeHelper.EmitModuleScopeProperties(scopeData, emitter); writer.WritePropertyName("properties"); { writer.WriteStartObject(); writer.WritePropertyName("expressionEvaluationOptions"); { writer.WriteStartObject(); this.emitter.EmitProperty("scope", "inner"); writer.WriteEndObject(); } this.emitter.EmitProperty("mode", "Incremental"); EmitModuleParameters(moduleSymbol); writer.WritePropertyName("template"); { var moduleSemanticModel = GetModuleSemanticModel(moduleSymbol); var moduleWriter = new TemplateWriter(writer, moduleSemanticModel); moduleWriter.Write(); } writer.WriteEndObject(); } this.EmitDependsOn(moduleSymbol); writer.WriteEndObject(); }
public void EmitModuleScopeProperty(TypeSymbol scopeType) { var scopeProperties = ScopeHelper.GetScopeProperties(this.converter, scopeType); if (scopeProperties == null) { return; } foreach (var(property, expression) in scopeProperties) { EmitProperty(property, () => { var serialized = ExpressionSerializer.SerializeExpression(expression); writer.WriteValue(serialized); }); } }
public static EmitLimitationInfo Calculate(SemanticModel model) { var diagnosticWriter = ToListDiagnosticWriter.Create(); var moduleScopeData = ScopeHelper.GetModuleScopeInfo(model, diagnosticWriter); var resourceScopeData = ScopeHelper.GetResourceScopeInfo(model, diagnosticWriter); DeployTimeConstantValidator.Validate(model, diagnosticWriter); ForSyntaxValidatorVisitor.Validate(model, diagnosticWriter); FunctionPlacementValidatorVisitor.Validate(model, diagnosticWriter); DetectDuplicateNames(model, diagnosticWriter, resourceScopeData, moduleScopeData); DetectIncorrectlyFormattedNames(model, diagnosticWriter); DetectUnexpectedResourceLoopInvariantProperties(model, diagnosticWriter); DetectUnexpectedModuleLoopInvariantProperties(model, diagnosticWriter); DetectUnsupportedModuleParameterAssignments(model, diagnosticWriter); return(new EmitLimitationInfo(diagnosticWriter.GetDiagnostics(), moduleScopeData, resourceScopeData)); }
public LanguageExpression GetLocallyScopedResourceIdExpression(ResourceDeclarationSyntax resourceSyntax, ResourceTypeReference typeReference) { IEnumerable <LanguageExpression> nameSegments; if (typeReference.Types.Length == 1) { nameSegments = GetResourceNameExpression(resourceSyntax).AsEnumerable(); } else { nameSegments = typeReference.Types.Select( (type, i) => new FunctionExpression( "split", new LanguageExpression[] { GetResourceNameExpression(resourceSyntax), new JTokenExpression("/") }, new LanguageExpression[] { new JTokenExpression(i) })); } return(ScopeHelper.FormatLocallyScopedResourceId( context.SemanticModel, typeReference.FullyQualifiedType, nameSegments)); }
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(); }
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 EmitModule(ModuleSymbol moduleSymbol) { writer.WriteStartObject(); if (moduleSymbol.DeclaringModule.IfCondition is IfConditionSyntax ifCondition) { this.emitter.EmitProperty("condition", ifCondition.ConditionExpression); } this.emitter.EmitProperty("type", NestedDeploymentResourceType); this.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). this.emitter.EmitObjectProperties((ObjectSyntax)moduleSymbol.DeclaringModule.Body, ModulePropertiesToOmit); var scopeData = context.ModuleScopeData[moduleSymbol]; ScopeHelper.EmitModuleScopeProperties(scopeData, emitter); if (scopeData.RequestedScope != ResourceScopeType.ResourceGroupScope) { // if we're deploying to a scope other than resource group, we need to supply a location if (this.context.SemanticModel.TargetScope == ResourceScopeType.ResourceGroupScope) { // the deployment() object at resource group scope does not contain a property named 'location', so we have to use resourceGroup().location this.emitter.EmitProperty("location", new FunctionExpression( "resourceGroup", new LanguageExpression[] { }, new LanguageExpression[] { new JTokenExpression("location") })); } else { // at all other scopes we can just use deployment().location this.emitter.EmitProperty("location", new FunctionExpression( "deployment", new LanguageExpression[] { }, new LanguageExpression[] { new JTokenExpression("location") })); } } writer.WritePropertyName("properties"); { writer.WriteStartObject(); writer.WritePropertyName("expressionEvaluationOptions"); { writer.WriteStartObject(); this.emitter.EmitProperty("scope", "inner"); writer.WriteEndObject(); } this.emitter.EmitProperty("mode", "Incremental"); EmitModuleParameters(moduleSymbol); writer.WritePropertyName("template"); { var moduleSemanticModel = GetModuleSemanticModel(moduleSymbol); var moduleWriter = new TemplateWriter(writer, moduleSemanticModel); moduleWriter.Write(); } writer.WriteEndObject(); } this.EmitDependsOn(moduleSymbol); writer.WriteEndObject(); }
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(); }