diff --git a/src/Microsoft.Tye.Core/Serialization/ConfigExtensionsParser.cs b/src/Microsoft.Tye.Core/Serialization/ConfigExtensionsParser.cs index 27d0f449..8a8e8566 100644 --- a/src/Microsoft.Tye.Core/Serialization/ConfigExtensionsParser.cs +++ b/src/Microsoft.Tye.Core/Serialization/ConfigExtensionsParser.cs @@ -16,13 +16,7 @@ namespace Tye.Serialization switch (child.NodeType) { case YamlNodeType.Mapping: - var extensionDictionary = new Dictionary(); - foreach (var mapping in (YamlMappingNode)child) - { - var key = YamlParser.GetScalarValue(mapping.Key); - extensionDictionary[key] = YamlParser.GetScalarValue(key, mapping.Value)!; - } - + var extensionDictionary = YamlParser.GetDictionary(child); extensions.Add(extensionDictionary); break; default: diff --git a/src/Microsoft.Tye.Core/Serialization/YamlParser.cs b/src/Microsoft.Tye.Core/Serialization/YamlParser.cs index 92f38e6d..259f0512 100644 --- a/src/Microsoft.Tye.Core/Serialization/YamlParser.cs +++ b/src/Microsoft.Tye.Core/Serialization/YamlParser.cs @@ -77,6 +77,32 @@ namespace Tye.Serialization return app; } + public static Dictionary GetDictionary(YamlNode node) + { + if (node.NodeType != YamlNodeType.Mapping) + { + throw new TyeYamlException(node.Start, + CoreStrings.FormatUnexpectedType(YamlNodeType.Mapping.ToString(), node.NodeType.ToString())); + } + + var dictionary = new Dictionary(); + + foreach (var mapping in (YamlMappingNode)node) + { + var key = YamlParser.GetScalarValue(mapping.Key); + + dictionary[key] = mapping.Value.NodeType switch + { + YamlNodeType.Scalar => YamlParser.GetScalarValue(key, mapping.Value)!, + YamlNodeType.Mapping => YamlParser.GetDictionary(mapping.Value), + _ => throw new TyeYamlException(mapping.Value.Start, + CoreStrings.FormatUnexpectedType(YamlNodeType.Mapping.ToString(), mapping.Value.NodeType.ToString())) + }; + } + + return dictionary; + } + public static string GetScalarValue(YamlNode node) { if (node.NodeType != YamlNodeType.Scalar) diff --git a/src/Microsoft.Tye.Extensions/Dapr/DaprExtension.cs b/src/Microsoft.Tye.Extensions/Dapr/DaprExtension.cs index 6a00dd3b..dd0b9972 100644 --- a/src/Microsoft.Tye.Extensions/Dapr/DaprExtension.cs +++ b/src/Microsoft.Tye.Extensions/Dapr/DaprExtension.cs @@ -23,14 +23,7 @@ namespace Microsoft.Tye.Extensions.Dapr { await VerifyDaprInitialized(context); - int? daprPlacementPort = null; - - // see if a placement port number has been defined - if (config.Data.TryGetValue("placement-port", out var obj) && obj?.ToString() is string && int.TryParse(obj.ToString(), out var customPlacementPort)) - { - context.Output.WriteDebugLine($"Using Dapr placement service host port {customPlacementPort} from 'placement-port'"); - daprPlacementPort = customPlacementPort; - } + var extensionConfiguration = DaprExtensionConfigurationReader.ReadConfiguration(config.Data); // For local run, enumerate all projects, and add services for each dapr proxy. var projects = context.Application.Services.OfType().Cast(); @@ -39,10 +32,25 @@ namespace Microsoft.Tye.Extensions.Dapr foreach (var project in services) { - // Dapr requires http. If this project isn't listening to HTTP then it's not daprized. + extensionConfiguration.Services.TryGetValue(project.Name, out DaprExtensionServiceConfiguration? serviceConfiguration); + + if (serviceConfiguration?.Enabled != null && serviceConfiguration.Enabled.Value == false) + { + context.Output.WriteDebugLine($"Dapr has been disabled for service {project.Name}."); + continue; + } + var httpBinding = project.Bindings.FirstOrDefault(b => b.Protocol == "http"); - if (httpBinding == null) + + if (httpBinding == null && project.Bindings.Count == 1 && project.Bindings[0].Protocol == null) + { + // Assume the only untyped binding is HTTP... + httpBinding = project.Bindings[0]; + } + + if (httpBinding == null && (serviceConfiguration?.Enabled == null || !serviceConfiguration.Enabled.Value)) { + context.Output.WriteDebugLine($"Dapr has been disabled for unbound service {project.Name}."); continue; } @@ -66,17 +74,25 @@ namespace Microsoft.Tye.Extensions.Dapr // These environment variables are replaced with environment variables // defined for this service. - Args = $"run --app-id {project.Name} --app-port %APP_PORT% --dapr-grpc-port %DAPR_GRPC_PORT% --dapr-http-port %DAPR_HTTP_PORT% --metrics-port %METRICS_PORT%", + Args = $"run --app-id {project.Name} --dapr-grpc-port %DAPR_GRPC_PORT% --dapr-http-port %DAPR_HTTP_PORT% --metrics-port %METRICS_PORT%", }; + if (httpBinding != null) + { + proxy.Args += $" --app-port %APP_PORT%"; + } + + var daprPlacementPort = serviceConfiguration?.PlacementPort ?? extensionConfiguration.PlacementPort; + if (daprPlacementPort.HasValue) { + context.Output.WriteDebugLine($"Using Dapr placement service host port {daprPlacementPort.Value} from 'placement-port' for service {project.Name}."); proxy.Args += $" --placement-host-address localhost:{daprPlacementPort.Value}"; } // When running locally `-config` specifies a filename, not a configuration name. By convention // we'll assume the filename and config name are the same. - if (config.Data.TryGetValue("config", out obj) && obj?.ToString() is string daprConfig) + if (config.Data.TryGetValue("config", out var obj) && obj?.ToString() is string daprConfig) { var configFile = Path.Combine(context.Application.Source.DirectoryName!, "components", $"{daprConfig}.yaml"); if (File.Exists(configFile)) @@ -132,15 +148,18 @@ namespace Microsoft.Tye.Extensions.Dapr }; proxy.Bindings.Add(metrics); - // Set APP_PORT based on the project's assigned port for http - var appPort = new EnvironmentVariableBuilder("APP_PORT") + if (httpBinding != null) { - Source = new EnvironmentVariableSourceBuilder(project.Name, binding: httpBinding.Name) + // Set APP_PORT based on the project's assigned port for http + var appPort = new EnvironmentVariableBuilder("APP_PORT") { - Kind = EnvironmentVariableSourceBuilder.SourceKind.Port, - }, - }; - proxy.EnvironmentVariables.Add(appPort); + Source = new EnvironmentVariableSourceBuilder(project.Name, binding: httpBinding.Name) + { + Kind = EnvironmentVariableSourceBuilder.SourceKind.Port, + }, + }; + proxy.EnvironmentVariables.Add(appPort); + } // Set DAPR_GRPC_PORT based on this service's assigned port var daprGrpcPort = new EnvironmentVariableBuilder("DAPR_GRPC_PORT") diff --git a/src/Microsoft.Tye.Extensions/Dapr/DaprExtensionConfiguration.cs b/src/Microsoft.Tye.Extensions/Dapr/DaprExtensionConfiguration.cs new file mode 100644 index 00000000..34af1a7a --- /dev/null +++ b/src/Microsoft.Tye.Extensions/Dapr/DaprExtensionConfiguration.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Microsoft.Tye.Extensions.Dapr +{ + internal abstract class DaprExtensionCommonConfiguration + { + public int? PlacementPort { get; set; } + } + + internal sealed class DaprExtensionServiceConfiguration : DaprExtensionCommonConfiguration + { + public bool? Enabled { get; set; } + } + + internal sealed class DaprExtensionConfiguration : DaprExtensionCommonConfiguration + { + public IReadOnlyDictionary Services { get; set; } + = new Dictionary(); + } +} diff --git a/src/Microsoft.Tye.Extensions/Dapr/DaprExtensionConfigurationReader.cs b/src/Microsoft.Tye.Extensions/Dapr/DaprExtensionConfigurationReader.cs new file mode 100644 index 00000000..bfec8848 --- /dev/null +++ b/src/Microsoft.Tye.Extensions/Dapr/DaprExtensionConfigurationReader.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Tye.Extensions.Dapr +{ + internal static class DaprExtensionConfigurationReader + { + public static DaprExtensionConfiguration ReadConfiguration(IDictionary rawConfiguration) + { + var configuration = new DaprExtensionConfiguration(); + + ReadCommonConfiguration(rawConfiguration, configuration); + + if (rawConfiguration.TryGetValue("services", out var servicesObject) && servicesObject is Dictionary rawServicesConfiguration) + { + var services = new Dictionary(); + + foreach (var kvp in rawServicesConfiguration) + { + if (kvp.Value is Dictionary rawServiceConfiguration) + { + var serviceConfiguration = new DaprExtensionServiceConfiguration(); + + ReadServiceConfiguration(rawServiceConfiguration, serviceConfiguration); + + services.Add(kvp.Key, serviceConfiguration); + } + } + + configuration.Services = services; + } + + return configuration; + } + + private static void ReadServiceConfiguration(IDictionary rawConfiguration, DaprExtensionServiceConfiguration serviceConfiguration) + { + ReadCommonConfiguration(rawConfiguration, serviceConfiguration); + + if (rawConfiguration.TryGetValue("enabled", out var obj) && obj is string && Boolean.TryParse(obj.ToString(), out var enabled)) + { + serviceConfiguration.Enabled = enabled; + } + } + + private static void ReadCommonConfiguration(IDictionary rawConfiguration, DaprExtensionCommonConfiguration commonConfiguration) + { + if (rawConfiguration.TryGetValue("placement-port", out var obj) && obj?.ToString() is string && int.TryParse(obj.ToString(), out var customPlacementPort)) + { + commonConfiguration.PlacementPort = customPlacementPort; + } + } + } +}