public async Task StartAsync(Tye.Hosting.Model.Application application)
 {
     foreach (var processor in _applicationProcessors)
     {
         await processor.StartAsync(application);
     }
 }
 public async Task StopAsync(Tye.Hosting.Model.Application application)
 {
     // Shutdown in the opposite order
     foreach (var processor in _applicationProcessors.Reverse())
     {
         await processor.StopAsync(application);
     }
 }
Exemplo n.º 3
0
        private static WebApplication BuildWebApplication(
            Tye.Hosting.Model.Application application,
            string[] args,
            ILogEventSink?sink)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Logging for this application
            builder.Host.UseSerilog((context, configuration) =>
            {
                configuration
                .MinimumLevel.Verbose()
                .Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore"))
                .Filter.ByExcluding(Matching.FromSource("Microsoft.Extensions"))
                .Filter.ByExcluding(Matching.FromSource("Microsoft.Hosting"))
                .Enrich
                .FromLogContext()
                .WriteTo
                .Console();

                if (sink is object)
                {
                    configuration.WriteTo.Sink(sink, LogEventLevel.Verbose);
                }
            });

            builder.Services.AddRazorPages(o => o.RootDirectory = "/Dashboard/Pages");

            builder.Services.AddServerSideBlazor();

            builder.Services.AddOptions <StaticFileOptions>()
            .PostConfigure(o =>
            {
                var fileProvider = new ManifestEmbeddedFileProvider(typeof(TyeHost).Assembly, "wwwroot");

                // Make sure we don't remove the existing file providers (blazor needs this)
                o.FileProvider = new CompositeFileProvider(o.FileProvider, fileProvider);
            });

            builder.Services.AddCors(
                options =>
            {
                options.AddPolicy(
                    "default",
                    policy =>
                {
                    policy
                    .AllowAnyOrigin()
                    .AllowAnyHeader()
                    .AllowAnyMethod();
                });
            });

            builder.Services.AddSingleton(application);
            var app = builder.Build();

            return(app);
        }
Exemplo n.º 4
0
        public Task StartAsync(Tye.Hosting.Model.Application application)
        {
            var tasks = new Task[application.Services.Count];
            var index = 0;

            foreach (var s in application.Services)
            {
                tasks[index++] = s.Value.Description.RunInfo is DockerRunInfo docker?StartContainerAsync(application, s.Value, docker) : Task.CompletedTask;
            }

            return(Task.WhenAll(tasks));
        }
Exemplo n.º 5
0
        public Task StopAsync(Tye.Hosting.Model.Application application)
        {
            foreach (var service in application.Services.Values)
            {
                if (service.Items.TryGetValue(typeof(Subscription), out var item) && item is IDisposable disposable)
                {
                    disposable.Dispose();
                }
            }

            return(Task.CompletedTask);
        }
Exemplo n.º 6
0
        public Task StartAsync(Tye.Hosting.Model.Application application)
        {
            foreach (var service in application.Services.Values)
            {
                if (service.Description.RunInfo is null)
                {
                    continue;
                }

                service.Items[typeof(Subscription)] = service.ReplicaEvents.Subscribe(OnReplicaChanged);
            }

            return(Task.CompletedTask);
        }
Exemplo n.º 7
0
        public Task StopAsync(Tye.Hosting.Model.Application application)
        {
            var services = application.Services;

            var index = 0;
            var tasks = new Task[services.Count];

            foreach (var s in services.Values)
            {
                var state = s;
                tasks[index++] = StopContainerAsync(state);
            }

            return(Task.WhenAll(tasks));
        }
Exemplo n.º 8
0
        public Task StartAsync(Tye.Hosting.Model.Application application)
        {
            var tasks = new Task[application.Services.Count];
            var index = 0;

            foreach (var s in application.Services)
            {
                tasks[index++] = s.Value.ServiceType switch
                {
                    ServiceType.Executable => LaunchService(application, s.Value),
                    ServiceType.Project => LaunchService(application, s.Value),

                    _ => Task.CompletedTask,
                };
            }

            return(Task.WhenAll(tasks));
        }
Exemplo n.º 9
0
        public Task StartAsync(Tye.Hosting.Model.Application application)
        {
            var tasks = new Task[application.Services.Count];
            var index = 0;

            foreach (var s in application.Services)
            {
                tasks[index++] = s.Value.ServiceType switch
                {
                    ServiceType.Container => Task.CompletedTask,
                    ServiceType.External => Task.CompletedTask,

                    ServiceType.Executable => LaunchService(application, s.Value),
                    ServiceType.Project => LaunchService(application, s.Value),

                    _ => throw new InvalidOperationException("Unknown ServiceType."),
                };
            }

            return(Task.WhenAll(tasks));
        }
Exemplo n.º 10
0
        public async Task StartAsync(Tye.Hosting.Model.Application application)
        {
            _host = new HostBuilder()
                    .ConfigureServer(server =>
            {
                server.UseSockets(sockets =>
                {
                    foreach (var service in application.Services.Values)
                    {
                        if (service.Description.RunInfo == null)
                        {
                            // We eventually want to proxy everything, this is temporary
                            continue;
                        }

                        static int GetNextPort()
                        {
                            // Let the OS assign the next available port. Unless we cycle through all ports
                            // on a test run, the OS will always increment the port number when making these calls.
                            // This prevents races in parallel test runs where a test is already bound to
                            // a given port, and a new test is able to bind to the same port due to port
                            // reuse being enabled by default by the OS.
                            using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                            socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
                            return(((IPEndPoint)socket.LocalEndPoint).Port);
                        }

                        foreach (var binding in service.Description.Bindings)
                        {
                            if (binding.Port == null && !binding.AutoAssignPort)
                            {
                                continue;
                            }

                            if (binding.Port == null)
                            {
                                binding.Port = GetNextPort();
                            }

                            if (binding.Protocol == "http" || (binding.Protocol == null && service.ServiceType == Model.ServiceType.Project))
                            {
                                binding.ContainerPort = 80;
                            }
                            else if (binding.Protocol == "https")
                            {
                                binding.ContainerPort = 443;
                            }

                            if (service.Description.Replicas == 1)
                            {
                                // No need to proxy
                                service.PortMap[binding.Port.Value] = new List <int> {
                                    binding.Port.Value
                                };
                                continue;
                            }

                            var ports = new List <int>();

                            for (var i = 0; i < service.Description.Replicas; i++)
                            {
                                // Reserve a port for each replica
                                var port = GetNextPort();
                                ports.Add(port);
                            }

                            _logger.LogInformation(
                                "Mapping external port {ExternalPort} to internal port(s) {InternalPorts} for {ServiceName} binding {BindingName}",
                                binding.Port,
                                string.Join(", ", ports.Select(p => p.ToString())),
                                service.Description.Name,
                                binding.Name ?? binding.Protocol);

                            service.PortMap[binding.Port.Value] = ports;

                            sockets.Listen(IPAddress.Loopback, binding.Port.Value, o =>
                            {
                                long count = 0;

                                // o.UseConnectionLogging("Tye.Proxy");

                                o.Run(async connection =>
                                {
                                    var notificationFeature = connection.Features.Get <IConnectionLifetimeNotificationFeature>();

                                    var next = (int)(Interlocked.Increment(ref count) % ports.Count);

                                    NetworkStream?targetStream = null;

                                    try
                                    {
                                        var target = new Socket(SocketType.Stream, ProtocolType.Tcp)
                                        {
                                            NoDelay = true
                                        };
                                        var port = ports[next];

                                        _logger.LogDebug("Attempting to connect to {ServiceName} listening on {ExternalPort}:{Port}", service.Description.Name, binding.Port, port);

                                        await target.ConnectAsync(IPAddress.Loopback, port);

                                        _logger.LogDebug("Successfully connected to {ServiceName} listening on {ExternalPort}:{Port}", service.Description.Name, binding.Port, port);

                                        targetStream = new NetworkStream(target, ownsSocket: true);
                                    }
                                    catch (Exception ex)
                                    {
                                        _logger.LogDebug(ex, "Proxy error for service {ServiceName}", service.Description.Name);

                                        if (targetStream is object)
                                        {
                                            await targetStream.DisposeAsync();
                                        }

                                        connection.Abort();
                                        return;
                                    }

                                    try
                                    {
                                        _logger.LogDebug("Proxying traffic to {ServiceName} {ExternalPort}:{InternalPort}", service.Description.Name, binding.Port, ports[next]);

                                        // external -> internal
                                        var reading = Task.Run(() => connection.Transport.Input.CopyToAsync(targetStream, notificationFeature.ConnectionClosedRequested));

                                        // internal -> external
                                        var writing = Task.Run(() => targetStream.CopyToAsync(connection.Transport.Output, notificationFeature.ConnectionClosedRequested));

                                        await Task.WhenAll(reading, writing);
                                    }
                                    catch (ConnectionResetException)
                                    {
                                        // Connection was reset
                                    }
                                    catch (IOException)
                                    {
                                        // Reset can also appear as an IOException with an inner SocketException
                                    }
                                    catch (OperationCanceledException ex)
                                    {
                                        if (!notificationFeature.ConnectionClosedRequested.IsCancellationRequested)
                                        {
                                            _logger.LogDebug(0, ex, "Proxy error for service {ServiceName}", service.Description.Name);
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        _logger.LogDebug(0, ex, "Proxy error for service {ServiceName}", service.Description.Name);
                                    }
                                    finally
                                    {
                                        await targetStream.DisposeAsync();
                                    }

                                    // This needs to reconnect to the target port(s) until its bound
                                    // it has to stop if the service is no longer running
                                });
                            });
                        }
                    }
Exemplo n.º 11
0
        private async Task LaunchService(Tye.Hosting.Model.Application application, Tye.Hosting.Model.Service service)
        {
            var serviceDescription = service.Description;
            var serviceName        = serviceDescription.Name;

            var path             = "";
            var workingDirectory = "";
            var args             = "";

            if (serviceDescription.RunInfo is ProjectRunInfo project)
            {
                var expandedProject = Environment.ExpandEnvironmentVariables(project.Project);
                var fullProjectPath = Path.GetFullPath(Path.Combine(application.ContextDirectory, expandedProject));
                path             = GetExePath(fullProjectPath);
                workingDirectory = Path.GetDirectoryName(fullProjectPath) !;
                args             = project.Args ?? "";
                service.Status.ProjectFilePath = fullProjectPath;
            }
            else if (serviceDescription.RunInfo is ExecutableRunInfo executable)
            {
                var expandedExecutable = Environment.ExpandEnvironmentVariables(executable.Executable);
                path = Path.GetExtension(expandedExecutable) == ".dll" ?
                       Path.GetFullPath(Path.Combine(application.ContextDirectory, expandedExecutable)) :
                       expandedExecutable;
                workingDirectory = executable.WorkingDirectory != null?
                                   Path.GetFullPath(Path.Combine(application.ContextDirectory, Environment.ExpandEnvironmentVariables(executable.WorkingDirectory))) :
                                       Path.GetDirectoryName(path) !;

                args = executable.Args ?? "";
            }
            else
            {
                throw new InvalidOperationException("Unsupported ServiceType.");
            }

            // If this is a dll then use dotnet to run it
            if (Path.GetExtension(path) == ".dll")
            {
                args = $"\"{path}\" {args}".Trim();
                path = "dotnet";
            }

            service.Status.ExecutablePath   = path;
            service.Status.WorkingDirectory = workingDirectory;
            service.Status.Args             = args;

            var processInfo = new ProcessInfo(new Task[service.Description.Replicas]);

            if (service.Status.ProjectFilePath != null &&
                service.Description.RunInfo is ProjectRunInfo project2 &&
                project2.Build &&
                _options.BuildProjects)
            {
                // Sometimes building can fail because of file locking (like files being open in VS)
                _logger.LogInformation("Building project {ProjectFile}", service.Status.ProjectFilePath);

                service.Logs.OnNext($"dotnet build \"{service.Status.ProjectFilePath}\" /nologo");

                var buildResult = await ProcessUtil.RunAsync("dotnet", $"build \"{service.Status.ProjectFilePath}\" /nologo", throwOnError : false);

                service.Logs.OnNext(buildResult.StandardOutput);

                if (buildResult.ExitCode != 0)
                {
                    _logger.LogInformation("Building {ProjectFile} failed with exit code {ExitCode}: \r\n" + buildResult.StandardOutput, service.Status.ProjectFilePath, buildResult.ExitCode);
                    return;
                }
            }

            async Task RunApplicationAsync(IEnumerable <(int Port, int BindingPort, string?Protocol)> ports)
            {
                // Make sure we yield before trying to start the process, this is important so we don't hang startup
                await Task.Yield();

                var hasPorts = ports.Any();

                var environment = new Dictionary <string, string>
                {
                    // Default to development environment
                    ["DOTNET_ENVIRONMENT"] = "Development"
                };

                // Set up environment variables to use the version of dotnet we're using to run
                // this is important for tests where we're not using a globally-installed dotnet.
                var dotnetRoot = GetDotnetRoot();

                if (dotnetRoot is object)
                {
                    environment["DOTNET_ROOT"] = dotnetRoot;
                    environment["DOTNET_MULTILEVEL_LOOKUP"] = "0";
                    environment["PATH"] = $"{dotnetRoot};{Environment.GetEnvironmentVariable("PATH")}";
                }

                application.PopulateEnvironment(service, (k, v) => environment[k] = v);

                if (_options.DebugMode && (_options.DebugAllServices || _options.ServicesToDebug.Contains(serviceName, StringComparer.OrdinalIgnoreCase)))
                {
                    environment["DOTNET_STARTUP_HOOKS"] = typeof(Hosting.Runtime.HostingRuntimeHelpers).Assembly.Location;
                }

                if (hasPorts)
                {
                    // These are the ports that the application should use for binding

                    // 1. Configure ASP.NET Core to bind to those same ports
                    environment["ASPNETCORE_URLS"] = string.Join(";", ports.Select(p => $"{p.Protocol ?? "http"}://localhost:{p.Port}"));

                    // Set the HTTPS port for the redirect middleware
                    foreach (var p in ports)
                    {
                        if (string.Equals(p.Protocol, "https", StringComparison.OrdinalIgnoreCase))
                        {
                            // We need to set the redirect URL to the exposed port so the redirect works cleanly
                            environment["HTTPS_PORT"] = p.BindingPort.ToString();
                        }
                    }

                    // 3. For non-ASP.NET Core apps, pass the same information in the PORT env variable as a semicolon separated list.
                    environment["PORT"] = string.Join(";", ports.Select(p => $"{p.Port}"));
                }

                while (!processInfo.StoppedTokenSource.IsCancellationRequested)
                {
                    var replica = serviceName + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower();
                    var status  = new ProcessStatus(service, replica);
                    service.Replicas[replica] = status;

                    service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status));

                    // This isn't your host name
                    environment["APP_INSTANCE"] = replica;

                    status.ExitCode    = null;
                    status.Pid         = null;
                    status.Environment = environment;

                    if (hasPorts)
                    {
                        status.Ports = ports.Select(p => p.Port);
                    }

                    _logger.LogInformation("Launching service {ServiceName}: {ExePath} {args}", replica, path, args);

                    try
                    {
                        service.Logs.OnNext($"[{replica}]:{path} {args}");

                        var result = await ProcessUtil.RunAsync(path, args,
                                                                environmentVariables : environment,
                                                                workingDirectory : workingDirectory,
                                                                outputDataReceived : data => service.Logs.OnNext($"[{replica}]: {data}"),
                                                                onStart : pid =>
                        {
                            if (hasPorts)
                            {
                                _logger.LogInformation("{ServiceName} running on process id {PID} bound to {Address}", replica, pid, string.Join(", ", ports.Select(p => $"{p.Protocol ?? "http"}://localhost:{p.Port}")));
                            }
                            else
                            {
                                _logger.LogInformation("{ServiceName} running on process id {PID}", replica, pid);
                            }

                            status.Pid = pid;

                            service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Started, status));
                        },
                                                                throwOnError : false,
                                                                cancellationToken : processInfo.StoppedTokenSource.Token);

                        status.ExitCode = result.ExitCode;

                        if (status.Pid != null)
                        {
                            service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Stopped, status));
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(0, ex, "Failed to launch process for service {ServiceName}", replica);

                        try
                        {
                            await Task.Delay(5000, processInfo.StoppedTokenSource.Token);
                        }
                        catch (OperationCanceledException)
                        {
                            // Swallow cancellation exceptions and continue
                        }
                    }

                    service.Restarts++;

                    if (status.ExitCode != null)
                    {
                        _logger.LogInformation("{ServiceName} process exited with exit code {ExitCode}", replica, status.ExitCode);
                    }

                    // Remove the replica from the set
                    service.Replicas.TryRemove(replica, out _);
                    service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status));
                }
            }

            if (serviceDescription.Bindings.Count > 0)
            {
                // Each replica is assigned a list of internal ports, one mapped to each external
                // port
                for (int i = 0; i < serviceDescription.Replicas; i++)
                {
                    var ports = new List <(int, int, string?)>();
                    foreach (var binding in serviceDescription.Bindings)
                    {
                        if (binding.Port == null)
                        {
                            continue;
                        }

                        ports.Add((service.PortMap[binding.Port.Value][i], binding.Port.Value, binding.Protocol));
                    }

                    processInfo.Tasks[i] = RunApplicationAsync(ports);
                }
            }
            else
            {
                for (int i = 0; i < service.Description.Replicas; i++)
                {
                    processInfo.Tasks[i] = RunApplicationAsync(Enumerable.Empty <(int, int, string?)>());
                }
            }

            service.Items[typeof(ProcessInfo)] = processInfo;
        }
Exemplo n.º 12
0
 public Task StopAsync(Tye.Hosting.Model.Application application)
 {
     return(KillRunningProcesses(application.Services));
 }
Exemplo n.º 13
0
        private async Task StartContainerAsync(Tye.Hosting.Model.Application application, Tye.Hosting.Model.Service service, DockerRunInfo docker)
        {
            if (!await DockerDetector.Instance.IsDockerInstalled.Value)
            {
                _logger.LogError("Unable to start docker container for service {ServiceName}, Docker is not installed.", service.Description.Name);

                service.Logs.OnNext($"Unable to start docker container for service {service.Description.Name}, Docker is not installed.");
                return;
            }

            if (!await DockerDetector.Instance.IsDockerConnectedToDaemon.Value)
            {
                _logger.LogError("Unable to start docker container for service {ServiceName}, Docker is not running.", service.Description.Name);

                service.Logs.OnNext($"Unable to start docker container for service {service.Description.Name}, Docker is not running.");
                return;
            }

            var serviceDescription   = service.Description;
            var environmentArguments = "";
            var volumes          = "";
            var workingDirectory = docker.WorkingDirectory != null ? $"-w {docker.WorkingDirectory}" : "";

            // This is .NET specific
            var userSecretStore = GetUserSecretsPathFromSecrets();

            if (!string.IsNullOrEmpty(userSecretStore))
            {
                // Map the user secrets on this drive to user secrets
                docker.VolumeMappings[userSecretStore] = "/root/.microsoft/usersecrets:ro";
            }

            var dockerInfo = new DockerInformation(new Task[service.Description.Replicas]);

            async Task RunDockerContainer(IEnumerable <(int Port, int?InternalPort, int BindingPort, string?Protocol)> ports)
            {
                var hasPorts = ports.Any();

                var replica = service.Description.Name.ToLower() + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower();
                var status  = new DockerStatus(service, replica);

                service.Replicas[replica] = status;

                service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status));

                var environment = new Dictionary <string, string>
                {
                    // Default to development environment
                    ["DOTNET_ENVIRONMENT"] = "Development",
                    // Remove the color codes from the console output
                    ["DOTNET_LOGGING__CONSOLE__DISABLECOLORS"] = "true"
                };

                var portString = "";

                if (hasPorts)
                {
                    status.Ports = ports.Select(p => p.Port);

                    // These are the ports that the application should use for binding

                    // 1. Tell the docker container what port to bind to
                    portString = string.Join(" ", ports.Select(p => $"-p {p.Port}:{p.InternalPort ?? p.Port}"));

                    // 2. Configure ASP.NET Core to bind to those same ports
                    environment["ASPNETCORE_URLS"] = string.Join(";", ports.Select(p => $"{p.Protocol ?? "http"}://*:{p.InternalPort ?? p.Port}"));

                    // Set the HTTPS port for the redirect middleware
                    foreach (var p in ports)
                    {
                        if (string.Equals(p.Protocol, "https", StringComparison.OrdinalIgnoreCase))
                        {
                            // We need to set the redirect URL to the exposed port so the redirect works cleanly
                            environment["HTTPS_PORT"] = p.BindingPort.ToString();
                        }
                    }

                    // 3. For non-ASP.NET Core apps, pass the same information in the PORT env variable as a semicolon separated list.
                    environment["PORT"] = string.Join(";", ports.Select(p => $"{p.InternalPort ?? p.Port}"));
                }

                // See: https://github.com/docker/for-linux/issues/264
                //
                // The way we do proxying here doesn't really work for multi-container scenarios on linux
                // without some more setup.
                application.PopulateEnvironment(service, (key, value) => environment[key] = value, "host.docker.internal");

                environment["APP_INSTANCE"] = replica;

                foreach (var pair in environment)
                {
                    environmentArguments += $"-e {pair.Key}={pair.Value} ";
                }

                foreach (var pair in docker.VolumeMappings)
                {
                    var sourcePath = Path.GetFullPath(Path.Combine(application.ContextDirectory, pair.Key.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)));
                    volumes += $"-v {sourcePath}:{pair.Value} ";
                }

                var command = $"run -d {workingDirectory} {volumes} {environmentArguments} {portString} --name {replica} --restart=unless-stopped {docker.Image} {docker.Args ?? ""}";

                _logger.LogInformation("Running docker command {Command}", command);

                service.Logs.OnNext($"[{replica}]: {command}");

                status.DockerCommand = command;

                var result = await ProcessUtil.RunAsync(
                    "docker",
                    command,
                    throwOnError : false,
                    cancellationToken : dockerInfo.StoppingTokenSource.Token,
                    outputDataReceived : data => service.Logs.OnNext($"[{replica}]: {data}"));

                if (result.ExitCode != 0)
                {
                    _logger.LogError("docker run failed for {ServiceName} with exit code {ExitCode}:" + result.StandardError, service.Description.Name, result.ExitCode);
                    service.Replicas.TryRemove(replica, out _);
                    service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status));

                    PrintStdOutAndErr(service, replica, result);
                    return;
                }

                var containerId = (string?)result.StandardOutput.Trim();

                // There's a race condition that sometimes makes us miss the output
                // so keep trying to get the container id
                while (string.IsNullOrEmpty(containerId))
                {
                    // Try to get the ID of the container
                    result = await ProcessUtil.RunAsync("docker", $"ps --no-trunc -f name={replica} --format " + "{{.ID}}");

                    containerId = result.ExitCode == 0 ? result.StandardOutput.Trim() : null;
                }

                var shortContainerId = containerId.Substring(0, Math.Min(12, containerId.Length));

                status.ContainerId = shortContainerId;

                _logger.LogInformation("Running container {ContainerName} with ID {ContainerId}", replica, shortContainerId);

                service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Started, status));

                _logger.LogInformation("Collecting docker logs for {ContainerName}.", replica);

                await ProcessUtil.RunAsync("docker", $"logs -f {containerId}",
                                           outputDataReceived : data => service.Logs.OnNext($"[{replica}]: {data}"),
                                           onStart : pid =>
                {
                    status.DockerLogsPid = pid;
                },
                                           throwOnError : false,
                                           cancellationToken : dockerInfo.StoppingTokenSource.Token);

                _logger.LogInformation("docker logs collection for {ContainerName} complete with exit code {ExitCode}", replica, result.ExitCode);

                // Docker has a tendency to get stuck so we're going to timeout this shutdown process
                var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

                _logger.LogInformation("Stopping container {ContainerName} with ID {ContainerId}", replica, shortContainerId);

                result = await ProcessUtil.RunAsync("docker", $"stop {containerId}", throwOnError : false, cancellationToken : timeoutCts.Token);

                PrintStdOutAndErr(service, replica, result);

                service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Stopped, status));

                _logger.LogInformation("Stopped container {ContainerName} with ID {ContainerId} exited with {ExitCode}", replica, shortContainerId, result.ExitCode);

                result = await ProcessUtil.RunAsync("docker", $"rm {containerId}", throwOnError : false, cancellationToken : timeoutCts.Token);

                PrintStdOutAndErr(service, replica, result);

                _logger.LogInformation("Removed container {ContainerName} with ID {ContainerId} exited with {ExitCode}", replica, shortContainerId, result.ExitCode);

                service.Replicas.TryRemove(replica, out _);

                service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status));
            };

            if (serviceDescription.Bindings.Count > 0)
            {
                // Each replica is assigned a list of internal ports, one mapped to each external
                // port
                for (var i = 0; i < serviceDescription.Replicas; i++)
                {
                    var ports = new List <(int, int?, int, string?)>();
                    foreach (var binding in serviceDescription.Bindings)
                    {
                        if (binding.Port == null)
                        {
                            continue;
                        }

                        ports.Add((service.PortMap[binding.Port.Value][i], binding.InternalPort, binding.Port.Value, binding.Protocol));
                    }

                    dockerInfo.Tasks[i] = RunDockerContainer(ports);
                }
            }
            else
            {
                for (var i = 0; i < service.Description.Replicas; i++)
                {
                    dockerInfo.Tasks[i] = RunDockerContainer(Enumerable.Empty <(int, int?, int, string?)>());
                }
            }

            service.Items[typeof(DockerInformation)] = dockerInfo;
        }
Exemplo n.º 14
0
 public TyeHost(Tye.Hosting.Model.Application application, string[] args)
     : this(application, args, new string[0])
 {
 }
Exemplo n.º 15
0
 public TyeHost(Tye.Hosting.Model.Application application, string[] args, string[] servicesToDebug)
 {
     _application     = application;
     _args            = args;
     _servicesToDebug = servicesToDebug;
 }
Exemplo n.º 16
0
        private async Task StartContainerAsync(Tye.Hosting.Model.Application application, Tye.Hosting.Model.Service service, DockerRunInfo docker)
        {
            if (!await DockerDetector.Instance.IsDockerInstalled.Value)
            {
                _logger.LogError("Unable to start docker container for service {ServiceName}, Docker is not installed.", service.Description.Name);

                service.Logs.OnNext($"Unable to start docker container for service {service.Description.Name}, Docker is not installed.");
                return;
            }

            if (!await DockerDetector.Instance.IsDockerConnectedToDaemon.Value)
            {
                _logger.LogError("Unable to start docker container for service {ServiceName}, Docker is not running.", service.Description.Name);

                service.Logs.OnNext($"Unable to start docker container for service {service.Description.Name}, Docker is not running.");
                return;
            }

            var serviceDescription   = service.Description;
            var environmentArguments = "";

            var dockerInfo = new DockerInformation(new Task[service.Description.Replicas]);

            async Task RunDockerContainer(IEnumerable <(int Port, int?InternalPort, int BindingPort, string?Protocol)> ports)
            {
                var hasPorts = ports.Any();

                var replica = service.Description.Name.ToLower() + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower();
                var status  = new DockerStatus(service, replica);

                service.Replicas[replica] = status;

                service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status));

                var environment = new Dictionary <string, string>
                {
                    // Default to development environment
                    ["DOTNET_ENVIRONMENT"] = "Development",
                    // Remove the color codes from the console output
                    ["DOTNET_LOGGING__CONSOLE__DISABLECOLORS"] = "true"
                };

                var portString = "";

                if (hasPorts)
                {
                    status.Ports = ports.Select(p => p.Port);

                    portString = string.Join(" ", ports.Select(p => $"-p {p.Port}:{p.InternalPort ?? p.Port}"));

                    foreach (var p in ports)
                    {
                        environment[$"{p.Protocol?.ToUpper() ?? "HTTP"}_PORT"] = p.BindingPort.ToString();
                    }
                }

                application.PopulateEnvironment(service, (key, value) => environment[key] = value, "host.docker.internal");

                environment["APP_INSTANCE"] = replica;

                foreach (var pair in environment)
                {
                    environmentArguments += $"-e {pair.Key}={pair.Value} ";
                }

                var command = $"run -d {environmentArguments} {portString} --name {replica} --restart=unless-stopped {docker.Image} {docker.Args ?? ""}";

                _logger.LogInformation("Running docker command {Command}", command);

                service.Logs.OnNext($"[{replica}]: {command}");

                status.DockerCommand = command;

                var result = await ProcessUtil.RunAsync(
                    "docker",
                    command,
                    throwOnError : false,
                    cancellationToken : dockerInfo.StoppingTokenSource.Token,
                    outputDataReceived : data => service.Logs.OnNext($"[{replica}]: {data}"));

                if (result.ExitCode != 0)
                {
                    _logger.LogError("docker run failed for {ServiceName} with exit code {ExitCode}:" + result.StandardError, service.Description.Name, result.ExitCode);
                    service.Replicas.TryRemove(replica, out _);
                    service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status));

                    PrintStdOutAndErr(service, replica, result);
                    return;
                }

                var containerId = (string?)result.StandardOutput.Trim();

                // There's a race condition that sometimes makes us miss the output
                // so keep trying to get the container id
                while (string.IsNullOrEmpty(containerId))
                {
                    // Try to get the ID of the container
                    result = await ProcessUtil.RunAsync("docker", $"ps --no-trunc -f name={replica} --format " + "{{.ID}}");

                    containerId = result.ExitCode == 0 ? result.StandardOutput.Trim() : null;
                }

                var shortContainerId = containerId.Substring(0, Math.Min(12, containerId.Length));

                status.ContainerId = shortContainerId;

                _logger.LogInformation("Running container {ContainerName} with ID {ContainerId}", replica, shortContainerId);

                service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Started, status));

                _logger.LogInformation("Collecting docker logs for {ContainerName}.", replica);

                await ProcessUtil.RunAsync("docker", $"logs -f {containerId}",
                                           outputDataReceived : data => service.Logs.OnNext($"[{replica}]: {data}"),
                                           onStart : pid =>
                {
                    status.DockerLogsPid = pid;
                },
                                           throwOnError : false,
                                           cancellationToken : dockerInfo.StoppingTokenSource.Token);

                _logger.LogInformation("docker logs collection for {ContainerName} complete with exit code {ExitCode}", replica, result.ExitCode);

                // Docker has a tendency to get stuck so we're going to timeout this shutdown process
                var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

                _logger.LogInformation("Stopping container {ContainerName} with ID {ContainerId}", replica, shortContainerId);

                result = await ProcessUtil.RunAsync("docker", $"stop {containerId}", throwOnError : false, cancellationToken : timeoutCts.Token);

                PrintStdOutAndErr(service, replica, result);

                service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Stopped, status));

                _logger.LogInformation("Stopped container {ContainerName} with ID {ContainerId} exited with {ExitCode}", replica, shortContainerId, result.ExitCode);

                result = await ProcessUtil.RunAsync("docker", $"rm {containerId}", throwOnError : false, cancellationToken : timeoutCts.Token);

                PrintStdOutAndErr(service, replica, result);

                _logger.LogInformation("Removed container {ContainerName} with ID {ContainerId} exited with {ExitCode}", replica, shortContainerId, result.ExitCode);

                service.Replicas.TryRemove(replica, out _);

                service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status));
            };

            if (serviceDescription.Bindings.Count > 0)
            {
                // Each replica is assigned a list of internal ports, one mapped to each external
                // port
                for (var i = 0; i < serviceDescription.Replicas; i++)
                {
                    var ports = new List <(int, int?, int, string?)>();
                    foreach (var binding in serviceDescription.Bindings)
                    {
                        if (binding.Port == null)
                        {
                            continue;
                        }

                        ports.Add((service.PortMap[binding.Port.Value][i], binding.InternalPort, binding.Port.Value, binding.Protocol));
                    }

                    dockerInfo.Tasks[i] = RunDockerContainer(ports);
                }
            }
            else
            {
                for (var i = 0; i < service.Description.Replicas; i++)
                {
                    dockerInfo.Tasks[i] = RunDockerContainer(Enumerable.Empty <(int, int?, int, string?)>());
                }
            }

            service.Items[typeof(DockerInformation)] = dockerInfo;
        }
Exemplo n.º 17
0
 public TyeHost(Tye.Hosting.Model.Application application, string[] args)
 {
     _application = application;
     _args        = args;
 }