Beispiel #1
0
        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>());
        }
Beispiel #2
0
        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>());
        }
Beispiel #5
0
        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();
            }
        }
Beispiel #7
0
        /// <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));
Beispiel #8
0
        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();
        }
Beispiel #9
0
        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();
        }
Beispiel #10
0
        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();
        }
Beispiel #13
0
        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();
        }
Beispiel #14
0
 private void EmitParameter(JsonTextWriter jsonWriter, ParameterSymbol parameterSymbol, ExpressionEmitter emitter)
 {
Beispiel #15
0
        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")));
                }
Beispiel #16
0
        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();
        }
Beispiel #17
0
        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}");
            }
        }
Beispiel #18
0
        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();
        }
Beispiel #20
0
 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();
        }
Beispiel #22
0
 public TemplateWriter(JsonTextWriter writer, SemanticModel semanticModel)
 {
     this.writer  = writer;
     this.context = new EmitterContext(semanticModel);
     this.emitter = new ExpressionEmitter(writer, context);
 }
Beispiel #23
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();
        }