From dda6cdab2abe71c5c0a5c8806a5a518a5656a7cf Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Fri, 7 Oct 2022 10:58:30 +0200 Subject: [PATCH 01/11] Merge ingresses with the same name or binding into 1 ingress service --- src/tye/ApplicationBuilderExtensions.cs | 38 +++++++++++++++---------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/tye/ApplicationBuilderExtensions.cs b/src/tye/ApplicationBuilderExtensions.cs index 6f1ff5c45..227eeef82 100644 --- a/src/tye/ApplicationBuilderExtensions.cs +++ b/src/tye/ApplicationBuilderExtensions.cs @@ -193,32 +193,40 @@ public static Application ToHostingApplication(this ApplicationBuilder applicati // Ingress get turned into services for hosting foreach (var ingress in application.Ingress) { - var rules = new List(); - - foreach (var rule in ingress.Rules) + services.TryGetValue(ingress.Name, out Service? service); + var description = service?.Description ?? new ServiceDescription(ingress.Name, new IngressRunInfo(new List())); + if (!(description.RunInfo is IngressRunInfo runinfo)) { - rules.Add(new IngressRule(rule.Host, rule.Path, rule.Service!, rule.PreservePath)); + throw new InvalidOperationException($"Service '{ingress.Name}' was already added but not as an ingress service."); } - var runInfo = new IngressRunInfo(rules); + description.Replicas = Math.Max(ingress.Replicas, description.Replicas); - var description = new ServiceDescription(ingress.Name, runInfo) + foreach (var rule in ingress.Rules) { - Replicas = ingress.Replicas, - }; + runinfo.Rules.Add(new IngressRule(rule.Host, rule.Path, rule.Service!, rule.PreservePath)); + } foreach (var binding in ingress.Bindings) { - description.Bindings.Add(new ServiceBinding() + //Match on name or all other properties to combind bindings to 1 instance so we can have http://*.80 listen to more then 1 service from different include files + if (!description.Bindings.Any(b => (!string.IsNullOrEmpty(b.Name) && b.Name == binding.Name) || + (b.Port == binding.Port && b.Protocol == binding.Protocol && b.IPAddress == binding.IPAddress))) { - Name = binding.Name, - Port = binding.Port, - Protocol = binding.Protocol, - IPAddress = binding.IPAddress, - }); + description.Bindings.Add(new ServiceBinding() + { + Name = binding.Name, + Port = binding.Port, + Protocol = binding.Protocol, + IPAddress = binding.IPAddress, + }); + } } - services.Add(ingress.Name, new Service(description, ServiceSource.Host)); + if (service == null) + { + services.Add(ingress.Name, new Service(description, ServiceSource.Host)); + } } return new Application(application.Name, application.Source, application.DashboardPort, services, application.ContainerEngine) From e5cd260c6b2582d8343a66965fc9accf67c979a6 Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Mon, 28 Nov 2022 11:13:08 +0100 Subject: [PATCH 02/11] Add the ingress urls to the index-view --- .../Dashboard/Pages/Index.razor | 80 ++++++++++++++----- 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor index cf06ba18b..7956075d0 100644 --- a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor +++ b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor @@ -51,30 +51,17 @@ } - @if (service.Description.Bindings.Any()) + @if (GetUrls(service) is IEnumerable urls && urls.Any()) { - foreach (var b in service.Description.Bindings) + foreach (var url in urls) { - if (b.Port != null) + if (url.StartsWith("http://") || url.StartsWith("https://")) { - if (b.Protocol == "http" || b.Protocol == "https") - { - var url = GetUrl(b); - @url - foreach (var r in b.Routes) - { - var routeUrl = url + r; - @routeUrl - } - } - else - { - @GetUrl(b) - } + @url } else { - @b.ConnectionString + @url } } } @@ -101,17 +88,74 @@ @code { private List _subscriptions = new List(); + private List _ingressDescriptions = new List(); + private const string INGRESS_NAME = "INGRESS"; string GetUrl(ServiceBinding b) { return $"{(b.Protocol ?? "tcp")}://{b.Host ?? "localhost"}:{b.Port}"; } + IEnumerable GetUrls(Service service) + { + foreach (var binding in service.Description.Bindings) + { + if (binding.Port != null) + { + var url = GetUrl(binding); + yield return url; + foreach (var r in binding.Routes) + { + yield return url + r; + } + } + else if (!string.IsNullOrEmpty(binding.ConnectionString)) + { + yield return binding.ConnectionString; + } + } + + foreach (ServiceDescription description in _ingressDescriptions) + { + foreach (IngressRule rule in (description?.RunInfo as IngressRunInfo)?.Rules ?? Enumerable.Empty()) + { + if (rule.Service == service.Description.Name) + { + ServiceBinding? mainBinding = description?.Bindings.FirstOrDefault(b => b.Protocol == "http") ?? + description?.Bindings.FirstOrDefault(b => b.Protocol == "https"); + + string? host = rule.Host; + if (string.IsNullOrEmpty(host)) + { + host = mainBinding?.Host ?? mainBinding?.IPAddress ?? "localhost"; + } + int port = mainBinding?.Port ?? 80; + + string url = GetUrl(new ServiceBinding + { + Port = port, + Host = host, + Protocol = mainBinding?.Protocol ?? "http" + }); + if (!string.IsNullOrEmpty(rule.Path)) + { + url += rule.Path; + } + yield return url; + } + } + } + } + protected override void OnInitialized() { foreach (var a in application.Services.Values) { _subscriptions.Add(a.ReplicaEvents.Subscribe(OnReplicaChanged)); + if (a.Description.RunInfo is IngressRunInfo) + { + _ingressDescriptions.Add(a.Description); + } } } From 9c6a246c51346281b488bf663e1748b60f0871ef Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Wed, 21 Dec 2022 16:08:34 +0100 Subject: [PATCH 03/11] Don't use cookies on the proxy, just pass through the cookie header --- src/Microsoft.Tye.Hosting/HttpProxyService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Tye.Hosting/HttpProxyService.cs b/src/Microsoft.Tye.Hosting/HttpProxyService.cs index 794f338b6..9d9b57e70 100644 --- a/src/Microsoft.Tye.Hosting/HttpProxyService.cs +++ b/src/Microsoft.Tye.Hosting/HttpProxyService.cs @@ -43,7 +43,8 @@ public async Task StartAsync(Application application) { AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, - UseProxy = false + UseProxy = false, + UseCookies = false, })); foreach (var service in application.Services.Values) From 7e3877bf65970418879c107de34de62805450f88 Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Thu, 22 Dec 2022 15:36:15 +0100 Subject: [PATCH 04/11] Add start/stop options to dashboard --- src/Microsoft.Tye.Core/ApplicationFactory.cs | 4 +- src/Microsoft.Tye.Core/HostOptions.cs | 2 + src/Microsoft.Tye.Core/ProcessUtil.cs | 2 +- .../Dashboard/Pages/Index.razor | 39 +++++++ src/Microsoft.Tye.Hosting/DockerRunner.cs | 55 +++++++-- .../DockerRunnerOptions.cs | 24 ++++ src/Microsoft.Tye.Hosting/ProcessRunner.cs | 106 ++++++++++++------ .../ProcessRunnerOptions.cs | 4 + src/Microsoft.Tye.Hosting/TyeHost.cs | 2 +- src/tye/Program.RunCommand.cs | 16 ++- src/tye/Properties/launchSettings.json | 5 +- 11 files changed, 210 insertions(+), 49 deletions(-) create mode 100644 src/Microsoft.Tye.Hosting/DockerRunnerOptions.cs diff --git a/src/Microsoft.Tye.Core/ApplicationFactory.cs b/src/Microsoft.Tye.Core/ApplicationFactory.cs index 5345d78c6..22c868ae6 100644 --- a/src/Microsoft.Tye.Core/ApplicationFactory.cs +++ b/src/Microsoft.Tye.Core/ApplicationFactory.cs @@ -292,8 +292,8 @@ bool IsAzureFunctionService(ConfigService service) { Args = configService.Args, WorkingDirectory = configService.WorkingDirectory != null ? - Path.GetFullPath(Path.Combine(config.Source.Directory!.FullName, Environment.ExpandEnvironmentVariables(configService.WorkingDirectory))) : - workingDirectory, + Path.GetFullPath(Path.Combine(config.Source.Directory!.FullName, Environment.ExpandEnvironmentVariables(configService.WorkingDirectory))) + : workingDirectory, Replicas = configService.Replicas ?? 1 }; service = executable; diff --git a/src/Microsoft.Tye.Core/HostOptions.cs b/src/Microsoft.Tye.Core/HostOptions.cs index 73135cfd8..d4b9ac06f 100644 --- a/src/Microsoft.Tye.Core/HostOptions.cs +++ b/src/Microsoft.Tye.Core/HostOptions.cs @@ -11,6 +11,8 @@ public class HostOptions public bool Dashboard { get; set; } public List Debug { get; } = new List(); + + public List NoStart { get; } = new List(); public string? DistributedTraceProvider { get; set; } diff --git a/src/Microsoft.Tye.Core/ProcessUtil.cs b/src/Microsoft.Tye.Core/ProcessUtil.cs index 50b3cb517..514cb0fa9 100644 --- a/src/Microsoft.Tye.Core/ProcessUtil.cs +++ b/src/Microsoft.Tye.Core/ProcessUtil.cs @@ -170,7 +170,7 @@ void WithOutputLock(Action action) // lock ensures we're reading output when WaitForExit is called in process.Exited event. lock (process) { - process.Start(); + process.Start(); onStart?.Invoke(process.Id); process.BeginOutputReadLine(); diff --git a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor index 7956075d0..447e238b3 100644 --- a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor +++ b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor @@ -19,6 +19,7 @@ Replicas Restarts Logs + Actions @@ -79,6 +80,20 @@ @service.Replicas.Count/@service.Description.Replicas @service.Restarts View + + @if (service.ServiceType == ServiceType.Container || service.ServiceType == ServiceType.Executable || service.ServiceType == ServiceType.Project || service.ServiceType == ServiceType.Function) + { + if (service.Replicas.Count == 0) { + + } else { + + } + } + } } @@ -159,6 +174,30 @@ } } + private async Task StartServiceAsync(Service service) + { + if (service.ServiceType == ServiceType.Container) + { + await DockerRunner.RestartContainerAsync(service); + } + else + { + await ProcessRunner.RestartService(service); + } + } + + private async Task StopServiceAsync(Service service) + { + if (service.ServiceType == ServiceType.Container) + { + await DockerRunner.StopContainerAsync(service); + } + else + { + await ProcessRunner.KillProcessAsync(service); + } + } + private void OnReplicaChanged(ReplicaEvent replicaEvent) { InvokeAsync(() => StateHasChanged()); diff --git a/src/Microsoft.Tye.Hosting/DockerRunner.cs b/src/Microsoft.Tye.Hosting/DockerRunner.cs index 2284f5517..dcc22fad2 100644 --- a/src/Microsoft.Tye.Hosting/DockerRunner.cs +++ b/src/Microsoft.Tye.Hosting/DockerRunner.cs @@ -24,11 +24,13 @@ public class DockerRunner : IApplicationProcessor private readonly ILogger _logger; private readonly ReplicaRegistry _replicaRegistry; + private readonly DockerRunnerOptions _options; - public DockerRunner(ILogger logger, ReplicaRegistry replicaRegistry) + public DockerRunner(ILogger logger, ReplicaRegistry replicaRegistry, DockerRunnerOptions options) { _logger = logger; _replicaRegistry = replicaRegistry; + _options = options; } public async Task StartAsync(Application application) @@ -537,6 +539,7 @@ Task DockerRunAsync(CancellationToken cancellationToken) return Task.WhenAll(tasks); } + var dockerInfo = new DockerInformation(); async Task BuildAndRunAsync(CancellationToken cancellationToken) { await DockerBuildAsync(cancellationToken); @@ -544,8 +547,13 @@ async Task BuildAndRunAsync(CancellationToken cancellationToken) await DockerRunAsync(cancellationToken); } - var dockerInfo = new DockerInformation(); - dockerInfo.Task = BuildAndRunAsync(dockerInfo.StoppingTokenSource.Token); + dockerInfo.SetBuildAndRunTask(BuildAndRunAsync); + + if (!_options.ManualStartServices && + !(_options.ServicesNotToStart?.Contains(service.Description.Name, StringComparer.OrdinalIgnoreCase) ?? false)) + { + dockerInfo.BuildAndRun(); + } service.Items[typeof(DockerInformation)] = dockerInfo; } @@ -587,13 +595,25 @@ private static void PrintStdOutAndErr(Service service, string replica, ProcessRe } } - private Task StopContainerAsync(Service service) + public static async Task RestartContainerAsync(Service service) + { + if (service.Items.TryGetValue(typeof(DockerInformation), out var value) && value is DockerInformation di) + { + await StopContainerAsync(service); + + di.BuildAndRun(); + service.Restarts++; + await di.Task; + } + } + + public static Task StopContainerAsync(Service service) { if (service.Items.TryGetValue(typeof(DockerInformation), out var value) && value is DockerInformation di) { - di.StoppingTokenSource.Cancel(); + di.CancelAndResetStoppingTokenSource(); + return di.Task ?? Task.CompletedTask; - return di.Task; } return Task.CompletedTask; @@ -601,8 +621,27 @@ private Task StopContainerAsync(Service service) private class DockerInformation { - public Task Task { get; set; } = default!; - public CancellationTokenSource StoppingTokenSource { get; } = new CancellationTokenSource(); + private Func? _buildAndRunAsync; + + public Task Task { get; private set; } = default!; + public CancellationTokenSource StoppingTokenSource { get; private set; } = new CancellationTokenSource(); + + public void SetBuildAndRunTask(Func func) + { + _buildAndRunAsync = func; + } + + public async void BuildAndRun() + { + Task = _buildAndRunAsync?.Invoke(StoppingTokenSource.Token) ?? Task.CompletedTask; + } + + internal void CancelAndResetStoppingTokenSource() + { + StoppingTokenSource.Cancel(); + StoppingTokenSource.Dispose(); + StoppingTokenSource = new CancellationTokenSource(); + } } private class DockerApplicationInformation diff --git a/src/Microsoft.Tye.Hosting/DockerRunnerOptions.cs b/src/Microsoft.Tye.Hosting/DockerRunnerOptions.cs new file mode 100644 index 000000000..64cd0eaa9 --- /dev/null +++ b/src/Microsoft.Tye.Hosting/DockerRunnerOptions.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; +using System.Linq; + +namespace Microsoft.Tye.Hosting +{ + public class DockerRunnerOptions + { + public bool ManualStartServices { get; set; } + public string[]? ServicesNotToStart { get; set; } + + public static DockerRunnerOptions FromHostOptions(HostOptions options) + { + return new DockerRunnerOptions + { + ManualStartServices = options.NoStart?.Contains("*", StringComparer.OrdinalIgnoreCase) ?? false, + ServicesNotToStart = options.NoStart?.ToArray() + }; + } + } +} diff --git a/src/Microsoft.Tye.Hosting/ProcessRunner.cs b/src/Microsoft.Tye.Hosting/ProcessRunner.cs index 4dc8f1248..bd7a30d80 100644 --- a/src/Microsoft.Tye.Hosting/ProcessRunner.cs +++ b/src/Microsoft.Tye.Hosting/ProcessRunner.cs @@ -179,10 +179,12 @@ service.Description.RunInfo is ProjectRunInfo project2 && } private void LaunchService(Application application, Service service) + { - var serviceDescription = service.Description; - var processInfo = new ProcessInfo(new Task[service.Description.Replicas]); - var serviceName = serviceDescription.Name; + var processInfo = service.Items.ContainsKey(typeof(ProcessInfo)) + ? (ProcessInfo)service.Items[typeof(ProcessInfo)] + : new ProcessInfo(new Task[service.Description.Replicas]); + var serviceName = service.Description.Name; // Set by BuildAndRunService var args = service.Status.Args!; @@ -297,7 +299,7 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string? try { service.Logs.OnNext($"[{replica}]:{path} {copiedArgs}"); - var processInfo = new ProcessSpec + var processSpec = new ProcessSpec { Executable = path, WorkingDirectory = workingDirectory, @@ -351,8 +353,10 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string? // Only increase backoff when not watching project as watch will wait for file changes before rebuild. backOff *= 2; } - - service.Restarts++; + if (!processInfo.StoppedTokenSource.IsCancellationRequested) + { + service.Restarts++; + } service.Replicas.TryRemove(replica, out var _); service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status)); @@ -375,6 +379,8 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string? } }; + + if (_options.Watch && (service.Description.RunInfo is ProjectRunInfo runInfo)) { var projectFile = runInfo.ProjectFile.FullName; @@ -385,7 +391,7 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string? environment["DOTNET_WATCH"] = "1"; await new DotNetWatcher(_logger) - .WatchAsync(processInfo, fileSetFactory, replica, status.StoppingTokenSource.Token); + .WatchAsync(processSpec, fileSetFactory, replica, status.StoppingTokenSource.Token); } else if (_options.Watch && (service.Description.RunInfo is AzureFunctionRunInfo azureFunctionRunInfo) && !string.IsNullOrEmpty(azureFunctionRunInfo.ProjectFile)) { @@ -397,11 +403,11 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string? environment["DOTNET_WATCH"] = "1"; await new DotNetWatcher(_logger) - .WatchAsync(processInfo, fileSetFactory, replica, status.StoppingTokenSource.Token); + .WatchAsync(processSpec, fileSetFactory, replica, status.StoppingTokenSource.Token); } else { - await ProcessUtil.RunAsync(processInfo, status.StoppingTokenSource.Token, throwOnError: false); + await ProcessUtil.RunAsync(processSpec, status.StoppingTokenSource.Token, throwOnError: false); } } catch (Exception ex) @@ -429,50 +435,78 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string? } } - if (serviceDescription.Bindings.Count > 0) + void Start() { - // Each replica is assigned a list of internal ports, one mapped to each external - // port - for (int i = 0; i < serviceDescription.Replicas; i++) + if (service.Description!.Bindings.Count > 0) { - var ports = new List<(int, int, string?, string?)>(); - foreach (var binding in serviceDescription.Bindings) + // Each replica is assigned a list of internal ports, one mapped to each external + // port + for (int i = 0; i < service.Description.Replicas; i++) { - if (binding.Port == null) + var ports = new List<(int, int, string?, string?)>(); + foreach (var binding in service.Description.Bindings) { - continue; + if (binding.Port == null) + { + continue; + } + + ports.Add((binding.Port.Value, binding.ReplicaPorts[i], binding.Protocol, binding.Host)); } - ports.Add((binding.Port.Value, binding.ReplicaPorts[i], binding.Protocol, binding.Host)); + processInfo!.Tasks[i] = RunApplicationAsync(ports, args); + } + } + else + { + for (int i = 0; i < service.Description.Replicas; i++) + { + processInfo!.Tasks[i] = RunApplicationAsync(Enumerable.Empty<(int, int, string?, string?)>(), args); } - - processInfo.Tasks[i] = RunApplicationAsync(ports, args); } } + + processInfo.Start = Start; + service.Items[typeof(ProcessInfo)] = processInfo; + if (!_options.ManualStartServices && + !(_options.ServicesNotToStart?.Contains(serviceName, StringComparer.OrdinalIgnoreCase) ?? false)) + { + processInfo.Start(); + } else { - for (int i = 0; i < service.Description.Replicas; i++) + for (var i=0;i(), args); + processInfo.Tasks[i] = Task.CompletedTask; } } + } - service.Items[typeof(ProcessInfo)] = processInfo; + public static async Task RestartService(Service service) + { + if (service.Items.TryGetValue(typeof(ProcessInfo), out var stateObj) && stateObj is ProcessInfo state) + { + await KillProcessAsync(service); + service.Restarts++; + state.Start(); + await Task.WhenAll(state.Tasks); + } } - private Task KillRunningProcesses(IDictionary services) + public static async Task KillProcessAsync(Service service) { - static Task KillProcessAsync(Service service) + if (service.Items.TryGetValue(typeof(ProcessInfo), out var stateObj) && stateObj is ProcessInfo state) { - if (service.Items.TryGetValue(typeof(ProcessInfo), out var stateObj) && stateObj is ProcessInfo state) - { - // Cancel the token before stopping the process - state.StoppedTokenSource.Cancel(); + // Cancel the token before stopping the process + state.StoppedTokenSource?.Cancel(); - return Task.WhenAll(state.Tasks); - } - return Task.CompletedTask; + await Task.WhenAll(state.Tasks); + state.ResetStoppedTokenSource(); } + } + + private Task KillRunningProcesses(IDictionary services) + { var index = 0; var tasks = new Task[services.Count]; @@ -541,7 +575,13 @@ public ProcessInfo(Task[] tasks) public Task[] Tasks { get; } - public CancellationTokenSource StoppedTokenSource { get; } = new CancellationTokenSource(); + public CancellationTokenSource StoppedTokenSource { get; private set; } = new CancellationTokenSource(); + public Action Start { get; internal set; } + internal void ResetStoppedTokenSource() + { + StoppedTokenSource.Dispose(); + StoppedTokenSource = new CancellationTokenSource(); + } } private class ProjectGroup diff --git a/src/Microsoft.Tye.Hosting/ProcessRunnerOptions.cs b/src/Microsoft.Tye.Hosting/ProcessRunnerOptions.cs index 699814279..5d758f83a 100644 --- a/src/Microsoft.Tye.Hosting/ProcessRunnerOptions.cs +++ b/src/Microsoft.Tye.Hosting/ProcessRunnerOptions.cs @@ -14,6 +14,8 @@ public class ProcessRunnerOptions public string[]? ServicesToDebug { get; set; } public bool DebugAllServices { get; set; } public bool Watch { get; set; } + public bool ManualStartServices { get; set; } + public string[]? ServicesNotToStart { get; set; } public static ProcessRunnerOptions FromHostOptions(HostOptions options) { @@ -23,6 +25,8 @@ public static ProcessRunnerOptions FromHostOptions(HostOptions options) DebugMode = options.Debug.Any(), ServicesToDebug = options.Debug.ToArray(), DebugAllServices = options.Debug?.Contains("*", StringComparer.OrdinalIgnoreCase) ?? false, + ManualStartServices = options.NoStart?.Contains("*", StringComparer.OrdinalIgnoreCase) ?? false, + ServicesNotToStart = options.NoStart?.ToArray(), Watch = options.Watch }; } diff --git a/src/Microsoft.Tye.Hosting/TyeHost.cs b/src/Microsoft.Tye.Hosting/TyeHost.cs index 86b142a3a..87cf0a0f3 100644 --- a/src/Microsoft.Tye.Hosting/TyeHost.cs +++ b/src/Microsoft.Tye.Hosting/TyeHost.cs @@ -318,7 +318,7 @@ private static AggregateApplicationProcessor CreateApplicationProcessor(ReplicaR new DockerImagePuller(logger), new FuncFinder(logger), new ReplicaMonitor(logger), - new DockerRunner(logger, replicaRegistry), + new DockerRunner(logger, replicaRegistry, DockerRunnerOptions.FromHostOptions(options)), new ProcessRunner(logger, replicaRegistry, ProcessRunnerOptions.FromHostOptions(options)) }; diff --git a/src/tye/Program.RunCommand.cs b/src/tye/Program.RunCommand.cs index dd59e428b..6a39cc15c 100644 --- a/src/tye/Program.RunCommand.cs +++ b/src/tye/Program.RunCommand.cs @@ -74,6 +74,15 @@ private static Command CreateRunCommand() Description = "Watches for code changes for all dotnet projects.", Required = false }, + new Option("--no-start") + { + Argument = new Argument("service") + { + Arity = ArgumentArity.ZeroOrMore, + }, + Description = "Skip automatic start for specific service(s). Specify \"*\" to skip start for all services.", + Required = false + }, StandardOptions.Framework, StandardOptions.Tags, StandardOptions.Verbosity, @@ -93,7 +102,7 @@ private static Command CreateRunCommand() var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(args.Tags); - var application = await ApplicationFactory.CreateAsync(output, args.Path, args.Framework, filter); + var application = await ApplicationFactory.CreateAsync(output, args.Path, args.Framework, filter); if (application.Services.Count == 0) { throw new CommandException($"No services found in \"{application.Source.Name}\""); @@ -111,9 +120,10 @@ private static Command CreateRunCommand() LoggingProvider = args.Logs, MetricsProvider = args.Metrics, LogVerbosity = args.Verbosity, - Watch = args.Watch + Watch = args.Watch, }; options.Debug.AddRange(args.Debug); + options.NoStart.AddRange(args.NoStart); await application.ProcessExtensionsAsync(options, output, ExtensionContext.OperationKind.LocalRun); @@ -153,6 +163,8 @@ private class RunCommandArguments public bool Dashboard { get; set; } public string[] Debug { get; set; } = Array.Empty(); + + public string[] NoStart { get; set; } = Array.Empty(); public string Dtrace { get; set; } = default!; diff --git a/src/tye/Properties/launchSettings.json b/src/tye/Properties/launchSettings.json index e28cc71a3..723d9b041 100644 --- a/src/tye/Properties/launchSettings.json +++ b/src/tye/Properties/launchSettings.json @@ -2,8 +2,9 @@ "profiles": { "tye": { "commandName": "Project", - "commandLineArgs": "run", - "workingDirectory": "..\\..\\samples\\frontend-backend\\" + "commandLineArgs": "run --no-start fhir --no-start ncarelegacy", + "workingDirectory": "C:\\Projects\\NcareAll\\.ncare", + "hotReloadEnabled": false } } } \ No newline at end of file From 9a7b1d1495af2abd977757f40a67ed02185e1f22 Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Thu, 22 Dec 2022 16:20:51 +0100 Subject: [PATCH 05/11] Not intended update --- src/tye/Properties/launchSettings.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tye/Properties/launchSettings.json b/src/tye/Properties/launchSettings.json index 723d9b041..e28cc71a3 100644 --- a/src/tye/Properties/launchSettings.json +++ b/src/tye/Properties/launchSettings.json @@ -2,9 +2,8 @@ "profiles": { "tye": { "commandName": "Project", - "commandLineArgs": "run --no-start fhir --no-start ncarelegacy", - "workingDirectory": "C:\\Projects\\NcareAll\\.ncare", - "hotReloadEnabled": false + "commandLineArgs": "run", + "workingDirectory": "..\\..\\samples\\frontend-backend\\" } } } \ No newline at end of file From 9283d490517e71a580ddfa4bbdb6194623afb88b Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Fri, 23 Dec 2022 11:53:05 +0100 Subject: [PATCH 06/11] Remove unneeded async --- src/Microsoft.Tye.Hosting/DockerRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Tye.Hosting/DockerRunner.cs b/src/Microsoft.Tye.Hosting/DockerRunner.cs index dcc22fad2..ac9f14f40 100644 --- a/src/Microsoft.Tye.Hosting/DockerRunner.cs +++ b/src/Microsoft.Tye.Hosting/DockerRunner.cs @@ -631,7 +631,7 @@ public void SetBuildAndRunTask(Func func) _buildAndRunAsync = func; } - public async void BuildAndRun() + public void BuildAndRun() { Task = _buildAndRunAsync?.Invoke(StoppingTokenSource.Token) ?? Task.CompletedTask; } From 91c38cc0234ea9e3ace9df4b1d16d97a43b3ec7d Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Fri, 23 Dec 2022 11:59:23 +0100 Subject: [PATCH 07/11] Fix nullables warnings --- src/Microsoft.Tye.Hosting/ProcessRunner.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Tye.Hosting/ProcessRunner.cs b/src/Microsoft.Tye.Hosting/ProcessRunner.cs index bd7a30d80..284f9b6a4 100644 --- a/src/Microsoft.Tye.Hosting/ProcessRunner.cs +++ b/src/Microsoft.Tye.Hosting/ProcessRunner.cs @@ -181,9 +181,9 @@ service.Description.RunInfo is ProjectRunInfo project2 && private void LaunchService(Application application, Service service) { - var processInfo = service.Items.ContainsKey(typeof(ProcessInfo)) + ProcessInfo processInfo = (service.Items.ContainsKey(typeof(ProcessInfo)) ? (ProcessInfo)service.Items[typeof(ProcessInfo)] - : new ProcessInfo(new Task[service.Description.Replicas]); + : null) ?? new ProcessInfo(new Task[service.Description.Replicas]); var serviceName = service.Description.Name; // Set by BuildAndRunService @@ -488,7 +488,7 @@ public static async Task RestartService(Service service) { await KillProcessAsync(service); service.Restarts++; - state.Start(); + state.Start?.Invoke(); await Task.WhenAll(state.Tasks); } } @@ -576,7 +576,7 @@ public ProcessInfo(Task[] tasks) public Task[] Tasks { get; } public CancellationTokenSource StoppedTokenSource { get; private set; } = new CancellationTokenSource(); - public Action Start { get; internal set; } + public Action? Start { get; internal set; } internal void ResetStoppedTokenSource() { StoppedTokenSource.Dispose(); From 23d1ecfcaa5a34884387bcb515037b39d3f142ff Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Fri, 23 Dec 2022 12:10:22 +0100 Subject: [PATCH 08/11] Fix build-checks --- src/Microsoft.Tye.Hosting/ProcessRunner.cs | 14 +++++--------- test/Test.Infrastructure/TestHelpers.cs | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Tye.Hosting/ProcessRunner.cs b/src/Microsoft.Tye.Hosting/ProcessRunner.cs index 284f9b6a4..8f5c2caf2 100644 --- a/src/Microsoft.Tye.Hosting/ProcessRunner.cs +++ b/src/Microsoft.Tye.Hosting/ProcessRunner.cs @@ -181,9 +181,8 @@ service.Description.RunInfo is ProjectRunInfo project2 && private void LaunchService(Application application, Service service) { - ProcessInfo processInfo = (service.Items.ContainsKey(typeof(ProcessInfo)) - ? (ProcessInfo)service.Items[typeof(ProcessInfo)] - : null) ?? new ProcessInfo(new Task[service.Description.Replicas]); + var processInfo = (service.Items.ContainsKey(typeof(ProcessInfo)) ? (ProcessInfo?)service.Items[typeof(ProcessInfo)] : null) + ?? new ProcessInfo(new Task[service.Description.Replicas]); var serviceName = service.Description.Name; // Set by BuildAndRunService @@ -260,7 +259,7 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string? var backOff = TimeSpan.FromSeconds(5); - while (!processInfo.StoppedTokenSource.IsCancellationRequested) + while (!processInfo!.StoppedTokenSource.IsCancellationRequested) { var replica = serviceName + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower(); var status = new ProcessStatus(service, replica); @@ -379,8 +378,6 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string? } }; - - if (_options.Watch && (service.Description.RunInfo is ProjectRunInfo runInfo)) { var projectFile = runInfo.ProjectFile.FullName; @@ -468,14 +465,13 @@ void Start() processInfo.Start = Start; service.Items[typeof(ProcessInfo)] = processInfo; - if (!_options.ManualStartServices && - !(_options.ServicesNotToStart?.Contains(serviceName, StringComparer.OrdinalIgnoreCase) ?? false)) + if (!_options.ManualStartServices && !(_options.ServicesNotToStart?.Contains(serviceName, StringComparer.OrdinalIgnoreCase) ?? false)) { processInfo.Start(); } else { - for (var i=0;i(), ContainerEngine.Default)); await dockerRunner.StartAsync(new Application(host.Application.Name, new FileInfo(host.Application.Source), null, new Dictionary(), ContainerEngine.Default)); From 52bba6fce338cb9a80b6bdf97f96d5eea3a3f142 Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Fri, 23 Dec 2022 12:15:58 +0100 Subject: [PATCH 09/11] Fix whitespaces formatting --- src/Microsoft.Tye.Core/HostOptions.cs | 2 +- src/Microsoft.Tye.Hosting/DockerRunnerOptions.cs | 2 +- src/Microsoft.Tye.Hosting/ProcessRunner.cs | 2 +- src/Microsoft.Tye.Hosting/ProcessRunnerOptions.cs | 2 +- src/tye/Program.RunCommand.cs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Tye.Core/HostOptions.cs b/src/Microsoft.Tye.Core/HostOptions.cs index d4b9ac06f..f6dc8d227 100644 --- a/src/Microsoft.Tye.Core/HostOptions.cs +++ b/src/Microsoft.Tye.Core/HostOptions.cs @@ -11,7 +11,7 @@ public class HostOptions public bool Dashboard { get; set; } public List Debug { get; } = new List(); - + public List NoStart { get; } = new List(); public string? DistributedTraceProvider { get; set; } diff --git a/src/Microsoft.Tye.Hosting/DockerRunnerOptions.cs b/src/Microsoft.Tye.Hosting/DockerRunnerOptions.cs index 64cd0eaa9..5c5a6bd39 100644 --- a/src/Microsoft.Tye.Hosting/DockerRunnerOptions.cs +++ b/src/Microsoft.Tye.Hosting/DockerRunnerOptions.cs @@ -16,7 +16,7 @@ public static DockerRunnerOptions FromHostOptions(HostOptions options) { return new DockerRunnerOptions { - ManualStartServices = options.NoStart?.Contains("*", StringComparer.OrdinalIgnoreCase) ?? false, + ManualStartServices = options.NoStart?.Contains("*", StringComparer.OrdinalIgnoreCase) ?? false, ServicesNotToStart = options.NoStart?.ToArray() }; } diff --git a/src/Microsoft.Tye.Hosting/ProcessRunner.cs b/src/Microsoft.Tye.Hosting/ProcessRunner.cs index 8f5c2caf2..de4458719 100644 --- a/src/Microsoft.Tye.Hosting/ProcessRunner.cs +++ b/src/Microsoft.Tye.Hosting/ProcessRunner.cs @@ -471,7 +471,7 @@ void Start() } else { - for (var i=0; i(); - + public string[] NoStart { get; set; } = Array.Empty(); public string Dtrace { get; set; } = default!; From ced7b4ca5bea8e5edd00e0bcfb212eba255505e0 Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Fri, 23 Dec 2022 16:38:08 +0100 Subject: [PATCH 10/11] That's not a flag-based enum --- src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor index b2d3b68fd..f3841e00d 100644 --- a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor +++ b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor @@ -117,12 +117,12 @@ @code { - static readonly ServiceType stopables = ServiceType.Container | ServiceType.Executable | ServiceType.Project | ServiceType.Function; + static readonly ServiceType[] stopables = new[] { ServiceType.Container, ServiceType.Executable, ServiceType.Project, ServiceType.Function }; private List _subscriptions = new List(); bool CanStartStop(Service? service) { - return service != null && ((stopables & service.ServiceType) == service.ServiceType); + return service != null && stopables.Contains(service.ServiceType); } string GetUrl(ServiceBinding b) From ead43d47e078c3d36daf27815dc88d75b148301b Mon Sep 17 00:00:00 2001 From: chris van de steeg Date: Mon, 2 Jan 2023 09:17:18 +0100 Subject: [PATCH 11/11] Remove ingress logic from this PR --- src/tye/ApplicationBuilderExtensions.cs | 38 ++++++++++--------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/tye/ApplicationBuilderExtensions.cs b/src/tye/ApplicationBuilderExtensions.cs index 227eeef82..6f1ff5c45 100644 --- a/src/tye/ApplicationBuilderExtensions.cs +++ b/src/tye/ApplicationBuilderExtensions.cs @@ -193,40 +193,32 @@ public static Application ToHostingApplication(this ApplicationBuilder applicati // Ingress get turned into services for hosting foreach (var ingress in application.Ingress) { - services.TryGetValue(ingress.Name, out Service? service); - var description = service?.Description ?? new ServiceDescription(ingress.Name, new IngressRunInfo(new List())); - if (!(description.RunInfo is IngressRunInfo runinfo)) + var rules = new List(); + + foreach (var rule in ingress.Rules) { - throw new InvalidOperationException($"Service '{ingress.Name}' was already added but not as an ingress service."); + rules.Add(new IngressRule(rule.Host, rule.Path, rule.Service!, rule.PreservePath)); } - description.Replicas = Math.Max(ingress.Replicas, description.Replicas); + var runInfo = new IngressRunInfo(rules); - foreach (var rule in ingress.Rules) + var description = new ServiceDescription(ingress.Name, runInfo) { - runinfo.Rules.Add(new IngressRule(rule.Host, rule.Path, rule.Service!, rule.PreservePath)); - } + Replicas = ingress.Replicas, + }; foreach (var binding in ingress.Bindings) { - //Match on name or all other properties to combind bindings to 1 instance so we can have http://*.80 listen to more then 1 service from different include files - if (!description.Bindings.Any(b => (!string.IsNullOrEmpty(b.Name) && b.Name == binding.Name) || - (b.Port == binding.Port && b.Protocol == binding.Protocol && b.IPAddress == binding.IPAddress))) + description.Bindings.Add(new ServiceBinding() { - description.Bindings.Add(new ServiceBinding() - { - Name = binding.Name, - Port = binding.Port, - Protocol = binding.Protocol, - IPAddress = binding.IPAddress, - }); - } + Name = binding.Name, + Port = binding.Port, + Protocol = binding.Protocol, + IPAddress = binding.IPAddress, + }); } - if (service == null) - { - services.Add(ingress.Name, new Service(description, ServiceSource.Host)); - } + services.Add(ingress.Name, new Service(description, ServiceSource.Host)); } return new Application(application.Name, application.Source, application.DashboardPort, services, application.ContainerEngine)