public static void ApplyContainerDefaults(Application application, ServiceEntry service, Project project, ContainerInfo container) { if (application is null) { throw new ArgumentNullException(nameof(application)); } if (service is null) { throw new ArgumentNullException(nameof(service)); } if (project is null) { throw new ArgumentNullException(nameof(project)); } if (container is null) { throw new ArgumentNullException(nameof(container)); } if (container.BaseImageName == null && project.Frameworks.Any(f => f.Name == "Microsoft.AspNetCore.App")) { container.BaseImageName = "mcr.microsoft.com/dotnet/core/aspnet"; } else if (container.BaseImageName == null) { container.BaseImageName = "mcr.microsoft.com/dotnet/core/runtime"; } if (container.BaseImageTag == null && project.TargetFramework == "netcoreapp3.1") { container.BaseImageTag = "3.1"; } else if (container.BaseImageTag == null && project.TargetFramework == "netcoreapp3.0") { container.BaseImageTag = "3.0"; } if (container.BaseImageTag == null) { throw new CommandException($"Unsupported TFM {project.TargetFramework}."); } container.BuildImageName ??= "mcr.microsoft.com/dotnet/core/sdk"; container.BuildImageTag ??= "3.1"; if (container.ImageName == null && application.Globals.Registry?.Hostname == null) { container.ImageName ??= service.Service.Name.ToLowerInvariant(); } else if (container.ImageName == null && application.Globals.Registry?.Hostname != null) { container.ImageName ??= $"{application.Globals.Registry?.Hostname}/{service.Service.Name.ToLowerInvariant()}"; } container.ImageTag ??= project.Version.Replace("+", "-"); }
public override Task ExecuteAsync(OutputContext output, Application application, ServiceEntry service) { var yaml = service.Outputs.OfType <IYamlManifestOutput>().ToArray(); if (yaml.Length == 0) { output.WriteDebugLine($"No yaml manifests found for service '{service.FriendlyName}'. Skipping."); return(Task.CompletedTask); } var outputFilePath = Path.Combine(OutputDirectory.FullName, $"{service.Service.Name}.yaml"); output.WriteInfoLine($"Writing output to '{outputFilePath}'."); if (File.Exists(outputFilePath) && !Force) { throw new CommandException($"'{service.Service.Name}.yaml' already exists for project. use '--force' to overwrite."); } File.Delete(outputFilePath); using var stream = File.OpenWrite(outputFilePath); using var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true); var yamlStream = new YamlStream(yaml.Select(y => y.Yaml)); yamlStream.Save(writer, assignAnchors: false); return(Task.CompletedTask); }
public override async Task ExecuteAsync(OutputContext output, Application application, ServiceEntry service) { if (SkipWithoutProject(output, service, out var project)) { return; } if (SkipWithoutContainerInfo(output, service, out var container)) { return; } var chartDirectory = Path.Combine(application.GetProjectDirectory(project), "charts"); if (Directory.Exists(chartDirectory) && !Force) { throw new CommandException("'charts' directory already exists for project. use '--force' to overwrite."); } else if (Directory.Exists(chartDirectory)) { Directory.Delete(chartDirectory, recursive: true); } var chart = new HelmChartStep(); await HelmChartGenerator.GenerateAsync( output, application, service, project, container, chart, new DirectoryInfo(chartDirectory)); output.WriteInfoLine($"Generated Helm Chart at '{Path.Combine(chartDirectory, chart.ChartName)}'."); }
public static async Task WriteDockerfileAsync(OutputContext output, Application application, ServiceEntry service, Project project, ContainerInfo container, string filePath) { if (output is null) { throw new ArgumentNullException(nameof(output)); } if (application is null) { throw new ArgumentNullException(nameof(application)); } if (service is null) { throw new ArgumentNullException(nameof(service)); } if (project is null) { throw new ArgumentNullException(nameof(project)); } if (container is null) { throw new ArgumentNullException(nameof(container)); } if (filePath is null) { throw new ArgumentNullException(nameof(filePath)); } using var stream = File.OpenWrite(filePath); using var writer = new StreamWriter(stream, encoding: Encoding.UTF8, leaveOpen: true); var entryPoint = Path.GetFileNameWithoutExtension(project.RelativeFilePath); output.WriteDebugLine($"Writing dockerfile to '{filePath}'."); if (container.UseMultiphaseDockerfile ?? true) { await WriteMultiphaseDockerfileAsync(writer, entryPoint, container); } else { await WriteLocalPublishDockerfileAsync(writer, entryPoint, container); } output.WriteDebugLine("Done writing dockerfile."); }
public static OamComponentOutput CreateOamComponent(OutputContext output, Application application, ServiceEntry service) { if (output is null) { throw new ArgumentNullException(nameof(output)); } if (application is null) { throw new ArgumentNullException(nameof(application)); } if (service is null) { throw new ArgumentNullException(nameof(service)); } var root = new YamlMappingNode(); root.Add("kind", "ComponentSchematic"); root.Add("apiVersion", "core.oam.dev/v1alpha1"); var metadata = new YamlMappingNode(); root.Add("metadata", metadata); metadata.Add("name", service.Service.Name); var spec = new YamlMappingNode(); root.Add("spec", spec); spec.Add("workloadType", "core.oam.dev/v1alpha1.Server"); var containers = new YamlSequenceNode(); spec.Add("containers", containers); var images = service.Outputs.OfType <DockerImageOutput>(); foreach (var image in images) { var container = new YamlMappingNode(); containers.Add(container); container.Add("name", service.Service.Name); // NOTE: to really support multiple images we'd need to generate unique names. container.Add("image", $"{image.ImageName}:{image.ImageTag}"); } return(new OamComponentOutput(service.Service.Name, new YamlDocument(root))); }
public static async Task BuildHelmChartAsync(OutputContext output, Application application, ServiceEntry service, Project project, ContainerInfo container, HelmChartStep chart) { if (output is null) { throw new ArgumentNullException(nameof(output)); } if (application is null) { throw new ArgumentNullException(nameof(application)); } if (service is null) { throw new ArgumentNullException(nameof(service)); } if (project is null) { throw new ArgumentNullException(nameof(project)); } if (container is null) { throw new ArgumentNullException(nameof(container)); } if (chart is null) { throw new ArgumentNullException(nameof(chart)); } var projectDirectory = Path.Combine(application.RootDirectory, Path.GetDirectoryName(project.RelativeFilePath) !); var outputDirectoryPath = Path.Combine(projectDirectory, "bin"); using var tempDirectory = TempDirectory.Create(); HelmChartGenerator.ApplyHelmChartDefaults(application, service, container, chart); var chartRoot = Path.Combine(projectDirectory, "charts"); var chartPath = Path.Combine(chartRoot, chart.ChartName); output.WriteDebugLine($"Looking for existing chart in '{chartPath}'."); if (Directory.Exists(chartPath)) { output.WriteDebugLine($"Found existing chart in '{chartPath}'."); } else { chartRoot = tempDirectory.DirectoryPath; chartPath = Path.Combine(chartRoot, chart.ChartName); output.WriteDebugLine($"Generating chart in '{chartPath}'."); await HelmChartGenerator.GenerateAsync(output, application, service, project, container, chart, new DirectoryInfo(tempDirectory.DirectoryPath)); } output.WriteDebugLine("Running 'helm package'."); output.WriteCommandLine("helm", $"package -d \"{outputDirectoryPath}\" --version {project.Version.Replace('+', '-')} --app-version {project.Version.Replace('+', '-')}"); var capture = output.Capture(); var exitCode = await Process.ExecuteAsync( "helm", $"package . -d \"{outputDirectoryPath}\" --version {project.Version.Replace('+', '-')} --app-version {project.Version.Replace('+', '-')}", workingDir : chartPath, stdOut : capture.StdOut, stdErr : capture.StdErr); output.WriteDebugLine($"Running 'helm package' exit code: {exitCode}"); if (exitCode != 0) { throw new CommandException("Running 'helm package' failed."); } output.WriteInfoLine($"Created Helm Chart: {Path.Combine(outputDirectoryPath, chart.ChartName + "-" + project.Version.Replace('+', '-') + ".tgz")}"); }
public static async Task BuildContainerImageAsync(OutputContext output, Application application, ServiceEntry service, Project project, ContainerInfo container) { if (output is null) { throw new ArgumentNullException(nameof(output)); } if (application is null) { throw new ArgumentNullException(nameof(application)); } if (service is null) { throw new ArgumentNullException(nameof(service)); } if (project is null) { throw new ArgumentNullException(nameof(project)); } if (container is null) { throw new ArgumentNullException(nameof(container)); } using var tempFile = TempFile.Create(); var dockerFilePath = Path.Combine(application.GetProjectDirectory(project), Path.GetDirectoryName(project.RelativeFilePath) !, "Dockerfile"); if (File.Exists(dockerFilePath)) { output.WriteDebugLine($"Using existing dockerfile '{dockerFilePath}'."); } else { await DockerfileGenerator.WriteDockerfileAsync(output, application, service, project, container, tempFile.FilePath); dockerFilePath = tempFile.FilePath; } output.WriteDebugLine("Running 'docker build'."); output.WriteCommandLine("docker", $"build . -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\""); var capture = output.Capture(); var exitCode = await Process.ExecuteAsync( $"docker", $"build . -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"", application.GetProjectDirectory(project), stdOut : capture.StdOut, stdErr : capture.StdErr); output.WriteDebugLine($"Done running 'docker build' exit code: {exitCode}"); if (exitCode != 0) { throw new CommandException("'docker build' failed."); } output.WriteInfoLine($"Created Docker Image: {container.ImageName}:{container.ImageTag}"); service.Outputs.Add(new DockerImageOutput(container.ImageName !, container.ImageTag !)); }
public override async Task ExecuteAsync(OutputContext output, Application application, ServiceEntry service) { if (SkipWithoutProject(output, service, out var project)) { return; } if (SkipWithoutContainerInfo(output, service, out var container)) { return; } if (container.UseMultiphaseDockerfile == false) { throw new CommandException("Generated Dockerfile workflow does not support single-phase Dockerfiles."); } container.UseMultiphaseDockerfile ??= true; var dockerFilePath = Path.Combine(application.GetProjectDirectory(project), "Dockerfile"); if (File.Exists(dockerFilePath) && !Force) { throw new CommandException("'Dockerfile' already exists for project. use '--force' to overwrite."); } File.Delete(dockerFilePath); await DockerfileGenerator.WriteDockerfileAsync(output, application, service, project, container, dockerFilePath); output.WriteInfoLine($"Generated Dockerfile at '{dockerFilePath}'."); }
public static async Task GenerateAsync(OutputContext output, Application application, ServiceEntry service, Project project, ContainerInfo container, HelmChartStep chart, DirectoryInfo outputDirectory) { if (output is null) { throw new ArgumentNullException(nameof(output)); } if (application is null) { throw new ArgumentNullException(nameof(application)); } if (service is null) { throw new ArgumentNullException(nameof(service)); } if (project is null) { throw new ArgumentNullException(nameof(project)); } if (container is null) { throw new ArgumentNullException(nameof(container)); } if (chart is null) { throw new ArgumentNullException(nameof(chart)); } if (outputDirectory is null) { throw new ArgumentNullException(nameof(outputDirectory)); } ApplyHelmChartDefaults(application, service, container, chart); // The directory with the charts needs to be the same as the chart name var chartDirectoryPath = Path.Combine(outputDirectory.FullName, chart.ChartName); Directory.CreateDirectory(chartDirectoryPath); var templateDirectoryPath = Path.Combine( Path.GetDirectoryName(typeof(HelmChartGenerator).Assembly.Location) !, "Templates", "Helm"); DirectoryCopy.Copy(templateDirectoryPath, chartDirectoryPath); // Write Chart.yaml // // apiVersion: v1 // name: <appname> // version: <version> // appVersion: <version> await File.WriteAllLinesAsync(Path.Combine(chartDirectoryPath, "Chart.yaml"), new[] { $"apiVersion: v1", $"name: {chart.ChartName}", $"# helm requires the version and appVersion to specified in Chart.yaml", $"# opulence will override these values when packaging the chart", $"version: {project.Version.Replace('+', '-')}", $"appVersion: {project.Version.Replace('+', '-')}" }); // Write values.yaml // // image: // repository: rynowak.azurecr.io/rochambot/gamemaster await File.WriteAllLinesAsync(Path.Combine(chartDirectoryPath, "values.yaml"), new[] { $"image:", $" repository: {container.ImageName}", }); }
public override async Task ExecuteAsync(OutputContext output, Application application, ServiceEntry service) { if (SkipWithoutProject(output, service, out var project)) { return; } if (SkipWithoutContainerInfo(output, service, out var container)) { return; } if (SkipForEnvironment(output, service, Environment)) { return; } await DockerContainerBuilder.BuildContainerImageAsync(output, application, service, project, container); }
public override Task ExecuteAsync(OutputContext output, Application application, ServiceEntry service) { if (SkipWithoutContainerOutput(output, service)) { return(Task.CompletedTask); } if (SkipForEnvironment(output, service, Environment)) { return(Task.CompletedTask); } service.Outputs.Add(KubernetesManifestGenerator.CreateDeployment(output, application, service)); service.Outputs.Add(KubernetesManifestGenerator.CreateService(output, application, service)); return(Task.CompletedTask); }
public abstract Task ExecuteAsync(OutputContext output, Application application, ServiceEntry service);
public override async Task ExecuteAsync(OutputContext output, Application application, ServiceEntry service) { var yaml = service.Outputs.OfType <IYamlManifestOutput>().ToArray(); if (yaml.Length == 0) { output.WriteDebugLine($"No yaml manifests found for service '{service.FriendlyName}'. Skipping."); return; } using var tempFile = TempFile.Create(); output.WriteDebugLine($"Writing output to '{tempFile.FilePath}'."); { using var stream = File.OpenWrite(tempFile.FilePath); using var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true); var yamlStream = new YamlStream(yaml.Select(y => y.Yaml)); yamlStream.Save(writer, assignAnchors: false); } // kubectl apply logic is implemented in the client in older versions of k8s. The capability // to get the same behavior in the server isn't present in every version that's relevant. // // https://kubernetes.io/docs/reference/using-api/api-concepts/#server-side-apply // output.WriteDebugLine("Running 'kubectl apply'."); output.WriteCommandLine("kubectl", $"apply -f \"{tempFile.FilePath}\""); var capture = output.Capture(); var exitCode = await Process.ExecuteAsync( $"kubectl", $"apply -f \"{tempFile.FilePath}\"", System.Environment.CurrentDirectory, stdOut : capture.StdOut, stdErr : capture.StdErr); output.WriteDebugLine($"Done running 'kubectl apply' exit code: {exitCode}"); if (exitCode != 0) { throw new CommandException("'kubectl apply' failed."); } output.WriteInfoLine($"Deployed service '{service.FriendlyName}'."); }