예제 #1
0
        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}");
        }
예제 #2
0
        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;
        }
예제 #4
0
        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}");
        }
예제 #5
0
        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));
        }
예제 #6
0
        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
            });
        }
예제 #7
0
        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}");
 }
예제 #10
0
        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));
        }
예제 #11
0
        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}");
        }