private static string DetermineContainerImage(ProjectRunInfo project) { var baseImageTag = !string.IsNullOrEmpty(project.ContainerBaseTag) ? project.ContainerBaseTag : project.TargetFrameworkVersion; string baseImage; if (!string.IsNullOrEmpty(project.ContainerBaseImage)) { baseImage = project.ContainerBaseImage; } else { if (DockerfileGenerator.TagIs50OrNewer(baseImageTag)) { // .NET 5.0+ does not have the /core in its image name baseImage = project.IsAspNet ? "mcr.microsoft.com/dotnet/aspnet" : "mcr.microsoft.com/dotnet/runtime"; } else { // .NET Core 2.1/3.1 has the /core in its image name baseImage = project.IsAspNet ? "mcr.microsoft.com/dotnet/core/aspnet" : "mcr.microsoft.com/dotnet/core/runtime"; } } return($"{baseImage}:{baseImageTag}"); }
public Tye.Hosting.Model.Application ToHostingApplication() { var services = new Dictionary <string, Tye.Hosting.Model.Service>(); foreach (var service in Services) { RunInfo?runInfo; if (service.External) { runInfo = null; } else if (service.DockerImage is object) { runInfo = new DockerRunInfo(service.DockerImage, service.Args); } else if (service.Executable is object) { runInfo = new ExecutableRunInfo(service.Executable, service.WorkingDirectory, service.Args); } else if (service.Project is object) { runInfo = new ProjectRunInfo(service.Project, service.Args, service.Build ?? true); } else { throw new InvalidOperationException($"Cannot figure out how to run service '{service.Name}'."); } var description = new Tye.Hosting.Model.ServiceDescription(service.Name, runInfo) { Replicas = service.Replicas ?? 1, }; foreach (var binding in service.Bindings) { description.Bindings.Add(new Tye.Hosting.Model.ServiceBinding() { ConnectionString = binding.ConnectionString, Host = binding.Host, AutoAssignPort = binding.AutoAssignPort, InternalPort = binding.InternalPort, Name = binding.Name, Port = binding.Port, Protocol = binding.Protocol, }); } foreach (var entry in service.Configuration) { description.Configuration.Add(new ConfigurationSource(entry.Name, entry.Value)); } services.Add(service.Name, new Tye.Hosting.Model.Service(description)); } return(new Tye.Hosting.Model.Application(Source, services)); }
private async Task TransformProjectToContainer(Service service, ProjectRunInfo project) { var serviceDescription = service.Description; var serviceName = serviceDescription.Name; service.Status.ProjectFilePath = project.ProjectFile.FullName; var targetFramework = project.TargetFramework; // Sometimes building can fail because of file locking (like files being open in VS) _logger.LogInformation("Publishing project {ProjectFile}", service.Status.ProjectFilePath); var publishCommand = $"publish \"{service.Status.ProjectFilePath}\" --framework {targetFramework} /nologo"; service.Logs.OnNext($"dotnet {publishCommand}"); var buildResult = await ProcessUtil.RunAsync("dotnet", publishCommand, throwOnError : false); service.Logs.OnNext(buildResult.StandardOutput); if (buildResult.ExitCode != 0) { _logger.LogInformation("Publishing {ProjectFile} failed with exit code {ExitCode}: \r\n" + buildResult.StandardOutput, service.Status.ProjectFilePath, buildResult.ExitCode); // Null out the RunInfo so that serviceDescription.RunInfo = null; return; } // We transform the project information into the following docker command: // docker run -w /app -v {publishDir}:/app -it {image} dotnet {outputfile}.dll var containerImage = DetermineContainerImage(project); var outputFileName = project.AssemblyName + ".dll"; var dockerRunInfo = new DockerRunInfo(containerImage, $"dotnet {outputFileName} {project.Args}") { WorkingDirectory = "/app" }; dockerRunInfo.VolumeMappings[project.PublishOutputPath] = "/app"; // Make volume mapping works when running as a container foreach (var mapping in project.VolumeMappings) { dockerRunInfo.VolumeMappings[mapping.Key] = mapping.Value; } // Change the project into a container info serviceDescription.RunInfo = dockerRunInfo; }
private static string DetermineContainerImage(ProjectRunInfo project) { string baseImage; if (!string.IsNullOrEmpty(project.ContainerBaseImage)) { baseImage = project.ContainerBaseImage; } else { baseImage = project.IsAspNet ? "mcr.microsoft.com/dotnet/core/aspnet" : "mcr.microsoft.com/dotnet/core/runtime"; } var baseImageTag = !string.IsNullOrEmpty(project.ContainerBaseTag) ? project.ContainerBaseTag : project.TargetFrameworkVersion; return($"{baseImage}:{baseImageTag}"); }
public async Task DockerBaseImageAndTagTest() { using var projectDirectory = CopyTestProjectDirectory(Path.Combine("frontend-backend", "backend")); var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "backend-baseimage.csproj")); var outputContext = new OutputContext(_sink, Verbosity.Debug); var application = await ApplicationFactory.CreateAsync(outputContext, projectFile); // Transform the backend into a docker image for testing var project = (ProjectServiceBuilder)application.Services.First(s => s.Name == "backend-baseimage"); // check ContainerInfo values Assert.True(string.Equals(project.ContainerInfo !.BaseImageName, "mcr.microsoft.com/dotnet/core/sdk")); Assert.True(string.Equals(project.ContainerInfo !.BaseImageTag, "3.1-buster")); // check projectInfo values var projectRunInfo = new ProjectRunInfo(project); Assert.True(string.Equals(projectRunInfo !.ContainerBaseImage, project.ContainerInfo.BaseImageName)); Assert.True(string.Equals(projectRunInfo !.ContainerBaseTag, project.ContainerInfo.BaseImageTag)); }
public static Application ToHostingApplication(this ApplicationBuilder application) { var services = new Dictionary <string, Service>(); foreach (var service in application.Services) { RunInfo?runInfo; Probe? liveness; Probe? readiness; int replicas; var env = new List <EnvironmentVariable>(); if (service is ExternalServiceBuilder) { runInfo = null; liveness = null; readiness = null; replicas = 1; } else if (service is DockerFileServiceBuilder dockerFile) { var dockerRunInfo = new DockerRunInfo(dockerFile.Image, dockerFile.Args) { IsAspNet = dockerFile.IsAspNet, BuildArgs = dockerFile.BuildArgs }; if (!string.IsNullOrEmpty(dockerFile.DockerFile)) { dockerRunInfo.DockerFile = new FileInfo(dockerFile.DockerFile); if (!string.IsNullOrEmpty(dockerFile.DockerFileContext)) { dockerRunInfo.DockerFileContext = new FileInfo(dockerFile.DockerFileContext); } else { dockerRunInfo.DockerFileContext = new FileInfo(dockerRunInfo.DockerFile.DirectoryName); } } foreach (var mapping in dockerFile.Volumes) { dockerRunInfo.VolumeMappings.Add(new DockerVolume(mapping.Source, mapping.Name, mapping.Target)); } runInfo = dockerRunInfo; replicas = dockerFile.Replicas; liveness = dockerFile.Liveness != null?GetProbeFromBuilder(dockerFile.Liveness) : null; readiness = dockerFile.Readiness != null?GetProbeFromBuilder(dockerFile.Readiness) : null; foreach (var entry in dockerFile.EnvironmentVariables) { env.Add(entry.ToHostingEnvironmentVariable()); } } else if (service is ContainerServiceBuilder container) { var dockerRunInfo = new DockerRunInfo(container.Image, container.Args) { IsAspNet = container.IsAspNet }; foreach (var mapping in container.Volumes) { dockerRunInfo.VolumeMappings.Add(new DockerVolume(mapping.Source, mapping.Name, mapping.Target)); } runInfo = dockerRunInfo; replicas = container.Replicas; liveness = container.Liveness != null?GetProbeFromBuilder(container.Liveness) : null; readiness = container.Readiness != null?GetProbeFromBuilder(container.Readiness) : null; foreach (var entry in container.EnvironmentVariables) { env.Add(entry.ToHostingEnvironmentVariable()); } } else if (service is ExecutableServiceBuilder executable) { runInfo = new ExecutableRunInfo(executable.Executable, executable.WorkingDirectory, executable.Args); replicas = executable.Replicas; liveness = executable.Liveness != null?GetProbeFromBuilder(executable.Liveness) : null; readiness = executable.Readiness != null?GetProbeFromBuilder(executable.Readiness) : null; foreach (var entry in executable.EnvironmentVariables) { env.Add(entry.ToHostingEnvironmentVariable()); } } else if (service is DotnetProjectServiceBuilder project) { if (project.TargetFrameworks.Length > 1) { throw new InvalidOperationException($"Unable to run {project.Name}. Multi-targeted projects are not supported."); } if (project.RunCommand == null) { throw new InvalidOperationException($"Unable to run {project.Name}. The project does not have a run command"); } var projectInfo = new ProjectRunInfo(project); foreach (var mapping in project.Volumes) { projectInfo.VolumeMappings.Add(new DockerVolume(mapping.Source, mapping.Name, mapping.Target)); } runInfo = projectInfo; replicas = project.Replicas; liveness = project.Liveness != null?GetProbeFromBuilder(project.Liveness) : null; readiness = project.Readiness != null?GetProbeFromBuilder(project.Readiness) : null; foreach (var entry in project.EnvironmentVariables) { env.Add(entry.ToHostingEnvironmentVariable()); } } else { throw new InvalidOperationException($"Cannot figure out how to run service '{service.Name}'."); } var description = new ServiceDescription(service.Name, runInfo) { Replicas = replicas, Liveness = liveness, Readiness = readiness }; description.Configuration.AddRange(env); description.Dependencies.AddRange(service.Dependencies); foreach (var binding in service.Bindings) { description.Bindings.Add(new ServiceBinding() { ConnectionString = binding.ConnectionString, Host = binding.Host, ContainerPort = binding.ContainerPort, Name = binding.Name, Port = binding.Port, Protocol = binding.Protocol, }); } services.Add(service.Name, new Service(description)); } // Ingress get turned into services for hosting foreach (var ingress in application.Ingress) { var rules = new List <IngressRule>(); foreach (var rule in ingress.Rules) { rules.Add(new IngressRule(rule.Host, rule.Path, rule.Service !)); } var runInfo = new IngressRunInfo(rules); var description = new ServiceDescription(ingress.Name, runInfo) { Replicas = ingress.Replicas, }; foreach (var binding in ingress.Bindings) { description.Bindings.Add(new ServiceBinding() { Name = binding.Name, Port = binding.Port, Protocol = binding.Protocol, }); } services.Add(ingress.Name, new Service(description)); } return(new Application(application.Source, services) { Network = application.Network }); }
private async Task TransformProjectToContainer(Service service, ProjectRunInfo project) { var serviceDescription = service.Description; var serviceName = serviceDescription.Name; service.Status.ProjectFilePath = project.ProjectFile.FullName; var targetFramework = project.TargetFramework; // Sometimes building can fail because of file locking (like files being open in VS) _logger.LogInformation("Publishing project {ProjectFile}", service.Status.ProjectFilePath); var buildArgs = project.BuildProperties.Aggregate(string.Empty, (current, property) => current + $" /p:{property.Key}={property.Value}").TrimStart(); var publishCommand = $"publish \"{service.Status.ProjectFilePath}\" --framework {targetFramework} {buildArgs} /nologo"; service.Logs.OnNext($"dotnet {publishCommand}"); var buildResult = await ProcessUtil.RunAsync("dotnet", publishCommand, throwOnError : false); service.Logs.OnNext(buildResult.StandardOutput); if (buildResult.ExitCode != 0) { _logger.LogInformation("Publishing {ProjectFile} failed with exit code {ExitCode}: \r\n" + buildResult.StandardOutput, service.Status.ProjectFilePath, buildResult.ExitCode); // Null out the RunInfo so that serviceDescription.RunInfo = null; return; } // We transform the project information into the following docker command: // docker run -w /app -v {publishDir}:/app -it {image} dotnet {outputfile}.dll var containerImage = DetermineContainerImage(project); var outputFileName = project.AssemblyName + ".dll"; var dockerRunInfo = new DockerRunInfo(containerImage, $"dotnet {outputFileName} {project.Args}") { WorkingDirectory = "/app", IsAspNet = project.IsAspNet }; dockerRunInfo.VolumeMappings.Add(new DockerVolume(source: project.PublishOutputPath, name: null, target: "/app")); // Make volume mapping works when running as a container dockerRunInfo.VolumeMappings.AddRange(project.VolumeMappings); // This is .NET specific var userSecretStore = GetUserSecretsPathFromSecrets(); if (!string.IsNullOrEmpty(userSecretStore)) { Directory.CreateDirectory(userSecretStore); // Map the user secrets on this drive to user secrets dockerRunInfo.VolumeMappings.Add(new DockerVolume(source: userSecretStore, name: null, target: "/root/.microsoft/usersecrets", readOnly: true)); } // Default to development environment serviceDescription.Configuration.Add(new EnvironmentVariable("DOTNET_ENVIRONMENT", "Development")); // Remove the color codes from the console output serviceDescription.Configuration.Add(new EnvironmentVariable("DOTNET_LOGGING__CONSOLE__DISABLECOLORS", "true")); if (project.IsAspNet) { serviceDescription.Configuration.Add(new EnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development")); serviceDescription.Configuration.Add(new EnvironmentVariable("ASPNETCORE_LOGGING__CONSOLE__DISABLECOLORS", "true")); } // If we have an https binding then export the dev cert and mount the volume into the container if (serviceDescription.Bindings.Any(b => string.Equals(b.Protocol, "https", StringComparison.OrdinalIgnoreCase))) { // We export the developer certificate from this machine var certPassword = Guid.NewGuid().ToString(); var certificateDirectory = _certificateDirectory.Value; var certificateFilePath = Path.Combine($"\"{certificateDirectory.DirectoryPath}", $"{project.AssemblyName}.pfx\""); await ProcessUtil.RunAsync("dotnet", $"dev-certs https -ep {certificateFilePath} -p {certPassword}"); serviceDescription.Configuration.Add(new EnvironmentVariable("Kestrel__Certificates__Development__Password", certPassword)); // Certificate Path: https://github.com/dotnet/aspnetcore/blob/a9d702624a02ad4ebf593d9bf9c1c69f5702a6f5/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs#L419 dockerRunInfo.VolumeMappings.Add(new DockerVolume(source: certificateDirectory.DirectoryPath, name: null, target: "/root/.aspnet/https", readOnly: true)); } // Change the project into a container info serviceDescription.RunInfo = dockerRunInfo; }
private async Task TransformProjectToContainer(Model.Application application, Model.Service service, ProjectRunInfo project) { var serviceDescription = service.Description; var serviceName = serviceDescription.Name; var expandedProject = Environment.ExpandEnvironmentVariables(project.Project); var fullProjectPath = Path.GetFullPath(Path.Combine(application.ContextDirectory, expandedProject)); service.Status.ProjectFilePath = fullProjectPath; // Sometimes building can fail because of file locking (like files being open in VS) _logger.LogInformation("Publishing project {ProjectFile}", service.Status.ProjectFilePath); service.Logs.OnNext($"dotnet publish \"{service.Status.ProjectFilePath}\" /nologo"); var buildResult = await ProcessUtil.RunAsync("dotnet", $"publish \"{service.Status.ProjectFilePath}\" /nologo", outputDataReceived : data => service.Logs.OnNext(data), throwOnError : false); if (buildResult.ExitCode != 0) { _logger.LogInformation("Publishing {ProjectFile} failed with exit code {ExitCode}: " + buildResult.StandardOutput + buildResult.StandardError, service.Status.ProjectFilePath, buildResult.ExitCode); return; } var targetFramework = GetTargetFramework(service.Status.ProjectFilePath); // We transform the project information into the following docker command: // docker run -w /app -v {projectDir}:/app -it {image} dotnet /app/bin/Debug/{tfm}/publish/{outputfile}.dll var containerImage = DetermineContainerImage(targetFramework); var outputFileName = Path.GetFileNameWithoutExtension(service.Status.ProjectFilePath) + ".dll"; var dockerRunInfo = new DockerRunInfo(containerImage, $"dotnet /app/bin/Debug/{targetFramework}/publish/{outputFileName} {project.Args}") { WorkingDirectory = "/app" }; dockerRunInfo.VolumeMappings[Path.GetDirectoryName(service.Status.ProjectFilePath) !] = "/app";
private static string DetermineContainerImage(ProjectRunInfo project) { return($"mcr.microsoft.com/dotnet/core/sdk:{project.TargetFrameworkVersion}"); }
public static Application ToHostingApplication(this ApplicationBuilder application) { var services = new Dictionary <string, Service>(); foreach (var service in application.Services) { RunInfo?runInfo; int replicas; var env = new List <EnvironmentVariable>(); if (service is ExternalServiceBuilder) { runInfo = null; replicas = 1; } else if (service is ContainerServiceBuilder container) { var dockerRunInfo = new DockerRunInfo(container.Image, container.Args); foreach (var mapping in container.Volumes) { dockerRunInfo.VolumeMappings.Add(new DockerVolume(mapping.Source, mapping.Name, mapping.Target)); } runInfo = dockerRunInfo; replicas = container.Replicas; foreach (var entry in container.EnvironmentVariables) { env.Add(entry.ToHostingEnvironmentVariable()); } } else if (service is ExecutableServiceBuilder executable) { runInfo = new ExecutableRunInfo(executable.Executable, executable.WorkingDirectory, executable.Args); replicas = executable.Replicas; foreach (var entry in executable.EnvironmentVariables) { env.Add(entry.ToHostingEnvironmentVariable()); } } else if (service is ProjectServiceBuilder project) { if (project.TargetFrameworks.Length > 1) { throw new InvalidOperationException($"Unable to run {project.Name}. Multi-targeted projects are not supported."); } if (project.RunCommand == null) { throw new InvalidOperationException($"Unable to run {project.Name}. The project does not have a run command"); } var projectInfo = new ProjectRunInfo(project); foreach (var mapping in project.Volumes) { projectInfo.VolumeMappings.Add(new DockerVolume(mapping.Source, mapping.Name, mapping.Target)); } runInfo = projectInfo; replicas = project.Replicas; foreach (var entry in project.EnvironmentVariables) { env.Add(entry.ToHostingEnvironmentVariable()); } } else { throw new InvalidOperationException($"Cannot figure out how to run service '{service.Name}'."); } var description = new ServiceDescription(service.Name, runInfo) { Replicas = replicas, }; description.Configuration.AddRange(env); foreach (var binding in service.Bindings) { description.Bindings.Add(new Hosting.Model.ServiceBinding() { ConnectionString = binding.ConnectionString, Host = binding.Host, AutoAssignPort = binding.AutoAssignPort, ContainerPort = binding.ContainerPort, Name = binding.Name, Port = binding.Port, Protocol = binding.Protocol, }); } services.Add(service.Name, new Service(description)); } // Ingress get turned into services for hosting foreach (var ingress in application.Ingress) { var rules = new List <IngressRule>(); foreach (var rule in ingress.Rules) { rules.Add(new IngressRule(rule.Host, rule.Path, rule.Service !)); } var runInfo = new IngressRunInfo(rules); var description = new ServiceDescription(ingress.Name, runInfo) { Replicas = ingress.Replicas, }; foreach (var binding in ingress.Bindings) { description.Bindings.Add(new Hosting.Model.ServiceBinding() { AutoAssignPort = binding.AutoAssignPort, Name = binding.Name, Port = binding.Port, Protocol = binding.Protocol, }); } services.Add(ingress.Name, new Service(description)); } return(new Application(application.Source, services)); }
private static string DetermineContainerImage(ProjectRunInfo project) { var baseImage = project.IsAspNet ? "mcr.microsoft.com/dotnet/core/aspnet" : "mcr.microsoft.com/dotnet/core/runtime"; return($"{baseImage}:{project.TargetFrameworkVersion}"); }