Beispiel #1
0
 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)));
        }
Beispiel #4
0
        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));
        }
Beispiel #5
0
        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));
        }
Beispiel #6
0
        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());
        }
Beispiel #7
0
        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));
        }
Beispiel #8
0
        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();
        }
Beispiel #9
0
        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);
                });
            }
        }
Beispiel #10
0
        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));
        }
Beispiel #11
0
        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();
        }
Beispiel #14
0
        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();
        }
Beispiel #15
0
        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();
        }