public SecretInputBinding(string name, string filename, ServiceBuilder service, BindingBuilder binding) { Name = name; Filename = filename; Service = service; Binding = binding; }
private static void AddProbe(ServiceBuilder service, YamlMappingNode container, ProbeBuilder builder, string name) { var probe = new YamlMappingNode(); container.Add(name, probe); if (builder.Http != null) { var builderHttp = builder.Http; var httpGet = new YamlMappingNode(); probe.Add("httpGet", httpGet); httpGet.Add("path", builderHttp.Path); if (builderHttp.Protocol != null) { httpGet.Add("scheme", builderHttp.Protocol.ToUpper()); } if (builderHttp.Port != null) { httpGet.Add("port", builderHttp.Port.ToString() !); } else { // If port is not given, we pull default port var binding = service.Bindings.First(b => builderHttp.Protocol == null || b.Protocol == builderHttp.Protocol); if (binding.Port != null) { httpGet.Add("port", binding.Port.Value.ToString()); } if (builderHttp.Protocol == null && binding.Protocol != null) { httpGet.Add("scheme", binding.Protocol.ToUpper()); } } if (builderHttp.Headers.Count > 0) { var headers = new YamlSequenceNode(); httpGet.Add("httpHeaders", headers); foreach (var builderHeader in builderHttp.Headers) { var header = new YamlMappingNode(); header.Add("name", builderHeader.Key); header.Add("value", builderHeader.Value.ToString() !); headers.Add(header); } } } probe.Add("initialDelaySeconds", builder.InitialDelay.ToString()); probe.Add("periodSeconds", builder.Period.ToString() !); probe.Add("successThreshold", builder.SuccessThreshold.ToString() !); probe.Add("failureThreshold", builder.FailureThreshold.ToString() !); }
public async Task ExecuteAsync(ServiceBuilder service) { using var tracker = output.BeginStep($"Processing Service '{service.Name}'..."); for (var i = 0; i < steps.Length; i++) { var step = steps[i]; using var stepTracker = output.BeginStep(step.DisplayText); await step.ExecuteAsync(output, application, service); stepTracker.MarkComplete(); } tracker.MarkComplete(); }
private static void AddProbe(ServiceBuilder service, YamlMappingNode container, ProbeBuilder builder, string probeName) { var probe = new YamlMappingNode(); container.Add(probeName, probe); if (builder.Http != null) { var builderHttp = builder.Http; var httpGet = new YamlMappingNode(); probe.Add("httpGet", httpGet); httpGet.Add("path", builderHttp.Path); if (builderHttp.Protocol != null) { httpGet.Add("scheme", builderHttp.Protocol.ToUpper()); } if (builderHttp.Port != null) { httpGet.Add("port", builderHttp.Port.ToString() !); } else { // If port is not given, we pull default port var binding = service.Bindings.First(b => builderHttp.Protocol == null || b.Protocol == builderHttp.Protocol); if (binding.Port != null) { httpGet.Add("port", binding.Port.Value.ToString()); } if (builderHttp.Protocol == null && binding.Protocol != null) { httpGet.Add("scheme", binding.Protocol.ToUpper()); } } if (builderHttp.Headers.Count > 0) { var headers = new YamlSequenceNode(); httpGet.Add("httpHeaders", headers); foreach (var(name, value) in builderHttp.Headers) { var header = new YamlMappingNode { { "name", name },
protected bool SkipWithoutProject(OutputContext output, ServiceBuilder service, [MaybeNullWhen(returnValue: true)] out ProjectServiceBuilder project) { if (output is null) { throw new ArgumentNullException(nameof(output)); } if (service is null) { throw new ArgumentNullException(nameof(service)); } if (service is ProjectServiceBuilder p) { project = p; return(false); } output.WriteInfoLine($"Service '{service.Name}' does not have a project associated. Skipping."); project = default !;
public static void ApplyHelmChartDefaults(ApplicationBuilder application, ServiceBuilder service, ContainerInfo container, HelmChartStep chart) { if (application is null) { throw new ArgumentNullException(nameof(application)); } if (service is null) { throw new ArgumentNullException(nameof(service)); } if (container is null) { throw new ArgumentNullException(nameof(container)); } if (chart is null) { throw new ArgumentNullException(nameof(chart)); } chart.ChartName ??= service.Name.ToLowerInvariant(); }
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutProject(output, service, out var project)) { return; } if (SkipWithoutContainerInfo(output, service, out var container)) { return; } if (!await DockerDetector.Instance.IsDockerInstalled.Value) { throw new CommandException($"Cannot generate a docker image for '{service.Name}' because docker is not installed."); } if (!await DockerDetector.Instance.IsDockerConnectedToDaemon.Value) { throw new CommandException($"Cannot generate a docker image for '{service.Name}' because docker is not running."); } await DockerContainerBuilder.BuildContainerImageAsync(output, application, project, container); }
public abstract Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service);
public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { // No need to do this computation for a non-project since we're not deploying it. if (!(service is ProjectServiceBuilder project)) { return(Task.CompletedTask); } // Compute ASPNETCORE_URLS based on the bindings exposed by *this* project. foreach (var binding in service.Bindings) { if (binding.Protocol == null && binding.ConnectionString == null) { binding.Protocol = "http"; } if (binding.Port == null && binding.Protocol == "http") { binding.Port = 80; } if (binding.Protocol == "http") { var port = binding.Port ?? 80; var urls = $"http://*{(port == 80 ? "" : (":" + port.ToString()))}"; project.EnvironmentVariables.Add(new EnvironmentVariableBuilder("ASPNETCORE_URLS") { Value = urls, }); project.EnvironmentVariables.Add(new EnvironmentVariableBuilder("PORT") { Value = port.ToString(CultureInfo.InvariantCulture), }); break; } } // Process bindings and turn them into environment variables and secrets. There's // some duplication with the code in m8s (Application.cs) for populating environments. // // service.Service.Bindings is the bindings OUT - this step computes bindings IN. var bindings = new ComputedBindings(); service.Outputs.Add(bindings); foreach (var other in application.Services) { if (object.ReferenceEquals(service, other)) { continue; } foreach (var binding in other.Bindings) { if (other is ProjectServiceBuilder) { // The other thing is a project, and will be deployed along with this // service. var configName = (binding.Name is null || binding.Name == other.Name) ? other.Name.ToUpperInvariant() : $"{other.Name.ToUpperInvariant()}__{binding.Name.ToUpperInvariant()}"; if (!string.IsNullOrEmpty(binding.ConnectionString)) { // Special case for connection strings bindings.Bindings.Add(new EnvironmentVariableInputBinding($"CONNECTIONSTRINGS__{configName}", binding.ConnectionString)); continue; } if (binding.Protocol == "https") { // We skip https for now in deployment, because the E2E requires certificates // and we haven't done those features yet. continue; } if (binding.Protocol == null) { binding.Protocol = "http"; } if (binding.Port == null && binding.Protocol == "http") { binding.Port = 80; } if (!string.IsNullOrEmpty(binding.Protocol)) { bindings.Bindings.Add(new EnvironmentVariableInputBinding($"SERVICE__{configName}__PROTOCOL", binding.Protocol)); } if (binding.Port != null) { bindings.Bindings.Add(new EnvironmentVariableInputBinding($"SERVICE__{configName}__PORT", binding.Port.Value.ToString())); } bindings.Bindings.Add(new EnvironmentVariableInputBinding($"SERVICE__{configName}__HOST", binding.Host ?? other.Name)); } else { // The other service is not a project, so we'll use secrets. if (!string.IsNullOrEmpty(binding.ConnectionString)) { bindings.Bindings.Add(new SecretConnectionStringInputBinding( name: $"binding-{Environment}-{(binding.Name == null ? other.Name.ToLowerInvariant() : (other.Name.ToLowerInvariant() + "-" + binding.Name.ToLowerInvariant()))}-secret", other, binding, keyname: $"CONNECTIONSTRINGS__{(binding.Name == null ? other.Name.ToUpperInvariant() : (other.Name.ToUpperInvariant() + "__" + binding.Name.ToUpperInvariant()))}")); } else { bindings.Bindings.Add(new SecretUrlInputBinding( name: $"binding-{Environment}-{(binding.Name == null ? other.Name.ToLowerInvariant() : (other.Name.ToLowerInvariant() + "-" + binding.Name.ToLowerInvariant()))}-secret", other, binding, keynamebase: $"SERVICE__{(binding.Name == null ? other.Name.ToUpperInvariant() : (other.Name.ToUpperInvariant() + "__" + binding.Name.ToUpperInvariant()))}")); } } } } return(Task.CompletedTask); }
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { var bindings = service.Outputs.OfType <ComputedBindings>().FirstOrDefault(); if (bindings is null) { return; } foreach (var binding in bindings.Bindings) { if (binding is SecretInputBinding secretInputBinding) { if (!Secrets.Add(secretInputBinding.Name)) { output.WriteDebugLine($"Already validated secret '{secretInputBinding.Name}'."); continue; } output.WriteDebugLine($"Validating secret '{secretInputBinding.Name}'."); var config = KubernetesClientConfiguration.BuildDefaultConfig(); // Workaround for https://github.com/kubernetes-client/csharp/issues/372 var store = await KubernetesClientConfiguration.LoadKubeConfigAsync(); var context = store.Contexts.Where(c => c.Name == config.CurrentContext).FirstOrDefault(); // Use namespace of application, or current context, or 'default' config.Namespace = application.Namespace; config.Namespace ??= context?.ContextDetails?.Namespace ?? "default"; var kubernetes = new Kubernetes(config); try { var result = await kubernetes.ReadNamespacedSecretWithHttpMessagesAsync(secretInputBinding.Name, config.Namespace); output.WriteInfoLine($"Found existing secret '{secretInputBinding.Name}'."); continue; } catch (HttpOperationException ex) when(ex.Response.StatusCode == HttpStatusCode.NotFound) { // The kubernetes client uses exceptions for 404s. } catch (Exception ex) { output.WriteDebugLine("Failed to query secret."); output.WriteDebugLine(ex.ToString()); throw new CommandException("Unable connect to kubernetes.", ex); } if (Force) { output.WriteDebugLine("Skipping because force was specified."); continue; } if (!Interactive && secretInputBinding is SecretConnectionStringInputBinding) { throw new CommandException( $"The secret '{secretInputBinding.Name}' used for service '{secretInputBinding.Service.Name}' is missing from the deployment environment. " + $"Rerun the command with --interactive to specify the value interactively, or with --force to skip validation. Alternatively " + $"use the following command to manually create the secret." + System.Environment.NewLine + $"kubectl create secret generic {secretInputBinding.Name} --namespace {config.Namespace} --from-literal=connectionstring=<value>"); } if (!Interactive && secretInputBinding is SecretUrlInputBinding) { throw new CommandException( $"The secret '{secretInputBinding.Name}' used for service '{secretInputBinding.Service.Name}' is missing from the deployment environment. " + $"Rerun the command with --interactive to specify the value interactively, or with --force to skip validation. Alternatively " + $"use the following command to manually create the secret." + System.Environment.NewLine + $"kubectl create secret generic {secretInputBinding.Name} --namespace {config.Namespace} --from-literal=protocol=<value> --from-literal=host=<value> --from-literal=port=<value>"); } V1Secret secret; if (secretInputBinding is SecretConnectionStringInputBinding) { // If we get here then we should create the secret. var text = output.Prompt($"Enter the connection string to use for service '{secretInputBinding.Service.Name}'", allowEmpty: true); if (string.IsNullOrWhiteSpace(text)) { output.WriteAlwaysLine($"Skipping creation of secret for '{secretInputBinding.Service.Name}'. This may prevent creation of pods until secrets are created."); output.WriteAlwaysLine($"Manually create a secret with:"); output.WriteAlwaysLine($"kubectl create secret generic {secretInputBinding.Name} --namespace {config.Namespace} --from-literal=connectionstring=<value>"); continue; } secret = new V1Secret(type: "Opaque", stringData: new Dictionary <string, string>() { { "connectionstring", text }, }); } else if (secretInputBinding is SecretUrlInputBinding) { // If we get here then we should create the secret. string text; Uri? uri = null; while (true) { text = output.Prompt($"Enter the URI to use for service '{secretInputBinding.Service.Name}'", allowEmpty: true); if (string.IsNullOrEmpty(text)) { break; // skip } else if (Uri.TryCreate(text, UriKind.Absolute, out uri)) { break; // success } output.WriteAlwaysLine($"Invalid URI: '{text}'"); } if (string.IsNullOrWhiteSpace(text)) { output.WriteAlwaysLine($"Skipping creation of secret for '{secretInputBinding.Service.Name}'. This may prevent creation of pods until secrets are created."); output.WriteAlwaysLine($"Manually create a secret with:"); output.WriteAlwaysLine($"kubectl create secret generic {secretInputBinding.Name} --namespace {config.Namespace} --from-literal=protocol=<value> --from-literal=host=<value> --from-literal=port=<value>"); continue; } secret = new V1Secret(type: "Opaque", stringData: new Dictionary <string, string>() { { "protocol", uri !.Scheme },
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder 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(project.ProjectFile.DirectoryName, "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, project, container, dockerFilePath); output.WriteInfoLine($"Generated Dockerfile at '{dockerFilePath}'."); }
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { var yaml = service.Outputs.OfType <IYamlManifestOutput>().ToArray(); if (yaml.Length == 0) { output.WriteDebugLine($"No yaml manifests found for service '{service.Name}'. Skipping."); return; } if (!await KubectlDetector.Instance.IsKubectlInstalled.Value) { throw new CommandException($"Cannot apply manifests for '{service.Name}' because kubectl is not installed."); } if (!await KubectlDetector.Instance.IsKubectlConnectedToCluster.Value) { throw new CommandException($"Cannot apply manifests for '{service.Name}' because kubectl is not connected to a cluster."); } using var tempFile = TempFile.Create(); output.WriteDebugLine($"Writing output to '{tempFile.FilePath}'."); { await using var stream = File.OpenWrite(tempFile.FilePath); await using var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: -1, 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.Name}'."); }
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutProject(output, service, out var project)) { return; } if (SkipWithoutContainerInfo(output, service, out var container)) { return; } var chartDirectory = Path.Combine(project.ProjectFile.DirectoryName, "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, project, container, chart, new DirectoryInfo(chartDirectory)); output.WriteInfoLine($"Generated Helm Chart at '{Path.Combine(chartDirectory, chart.ChartName)}'."); }
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutProject(output, service, out var _)) { return; } if (SkipWithoutContainerInfo(output, service, out var _)) { return; } foreach (var image in service.Outputs.OfType <DockerImageOutput>()) { await DockerPush.ExecuteAsync(output, image.ImageName, image.ImageTag); output.WriteInfoLine($"Pushed docker image: '{image.ImageName}:{image.ImageTag}'"); } }
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { var bindings = service.Outputs.OfType <ComputedBindings>().FirstOrDefault(); if (bindings is null) { return; } foreach (var binding in bindings.Bindings) { if (binding is SecretInputBinding secretInputBinding) { if (!Secrets.Add(secretInputBinding.Name)) { output.WriteDebugLine($"Already validated secret '{secretInputBinding.Name}'."); continue; } output.WriteDebugLine($"Validating secret '{secretInputBinding.Name}'."); var config = KubernetesClientConfiguration.BuildDefaultConfig(); // Workaround for https://github.com/kubernetes-client/csharp/issues/372 var store = await KubernetesClientConfiguration.LoadKubeConfigAsync(); var context = store.Contexts.Where(c => c.Name == config.CurrentContext).FirstOrDefault(); config.Namespace ??= context?.ContextDetails?.Namespace; var kubernetes = new Kubernetes(config); try { var result = await kubernetes.ReadNamespacedSecretWithHttpMessagesAsync(secretInputBinding.Name, config.Namespace ?? "default"); output.WriteInfoLine($"Found existing secret '{secretInputBinding.Name}'."); continue; } catch (HttpOperationException ex) when(ex.Response.StatusCode == HttpStatusCode.NotFound) { // The kubernetes client uses exceptions for 404s. } catch (Exception ex) { output.WriteDebugLine("Failed to query secret."); output.WriteDebugLine(ex.ToString()); throw new CommandException("Unable connect to kubernetes.", ex); } if (Force) { output.WriteDebugLine("Skipping because force was specified."); continue; } if (!Interactive) { throw new CommandException( $"The secret '{secretInputBinding.Name}' used for service '{secretInputBinding.Service.Name}' is missing from the deployment environment. " + $"Rerun the command with --interactive to specify the value interactively, or with --force to skip validation. Alternatively " + $"use the following command to manually create the secret." + System.Environment.NewLine + $"kubectl create secret generic {secretInputBinding.Name} --from-literal=connectionstring=<value>"); } // If we get here then we should create the secret. var text = output.Prompt($"Enter the connection string to use for service '{secretInputBinding.Service.Name}'", allowEmpty: true); if (string.IsNullOrWhiteSpace(text)) { output.WriteAlways($"Skipping creation of secret for '{secretInputBinding.Service.Name}'. This may prevent creation of pods until secrets are created."); output.WriteAlways($"Manually create a secret with:"); output.WriteAlways($"kubectl create secret generic {secretInputBinding.Name} --from-literal=connectionstring=<value>"); continue; } var secret = new V1Secret(type: "Opaque", stringData: new Dictionary <string, string>() { { "connectionstring", text }, }); secret.Metadata = new V1ObjectMeta(); secret.Metadata.Name = secretInputBinding.Name; output.WriteDebugLine($"Creating secret '{secret.Metadata.Name}'."); try { await kubernetes.CreateNamespacedSecretWithHttpMessagesAsync(secret, config.Namespace ?? "default"); output.WriteInfoLine($"Created secret '{secret.Metadata.Name}'."); } catch (Exception ex) { output.WriteDebugLine("Failed to create secret."); output.WriteDebugLine(ex.ToString()); throw new CommandException("Failed to create secret.", ex); } } } var yaml = service.Outputs.OfType <IYamlManifestOutput>().ToArray(); if (yaml.Length == 0) { output.WriteDebugLine($"No yaml manifests found for service '{service.Name}'. Skipping."); return; } using var tempFile = TempFile.Create(); output.WriteDebugLine($"Writing output to '{tempFile.FilePath}'."); { await using var stream = File.OpenWrite(tempFile.FilePath); await using var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: -1, 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.Name}'."); }
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutProject(output, service, out var project)) { return; } if (SkipWithoutContainerInfo(output, service, out var container)) { return; } if (container.UseMultiphaseDockerfile != false) { return; } // NOTE: we're intentionally not cleaning up here. It's the responsibility of whomever consumes // the publish output to do cleanup. var outputDirectory = TempDirectory.Create(); output.WriteDebugLine("Running 'dotnet publish'."); output.WriteCommandLine("dotnet", $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory.DirectoryPath}\""); var capture = output.Capture(); var exitCode = await Process.ExecuteAsync( $"dotnet", $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory.DirectoryPath}\"", project.ProjectFile.DirectoryName, stdOut : capture.StdOut, stdErr : capture.StdErr); output.WriteDebugLine($"Done running 'dotnet publish' exit code: {exitCode}"); if (exitCode != 0) { outputDirectory.Dispose(); throw new CommandException("'dotnet publish' failed."); } output.WriteDebugLine($"Created Publish Output: '{outputDirectory.DirectoryPath}'"); service.Outputs.Add(new ProjectPublishOutput(outputDirectory.DirectoryInfo)); }
public SecretUrlInputBinding(string name, ServiceBuilder service, BindingBuilder binding, string keynamebase) : base(name, service, binding) { KeyNameBase = keynamebase; }
public SecretConnectionStringInputBinding(string name, ServiceBuilder service, BindingBuilder binding, string keyname) : base(name, service, binding) { KeyName = keyname; }
protected SecretInputBinding(string name, ServiceBuilder service, BindingBuilder binding) { Name = name; Service = service; Binding = binding; }
public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutContainerOutput(output, service)) { return(Task.CompletedTask); } if (SkipWithoutProject(output, service, out var project)) { return(Task.CompletedTask); } var deployment = project.ManifestInfo?.Deployment; if (deployment is null) { return(Task.CompletedTask); } // Initialize defaults for deployment-related settings deployment.Labels.TryAdd("app.kubernetes.io/name", project.Name); deployment.Labels.TryAdd("app.kubernetes.io/part-of", application.Name); service.Outputs.Add(KubernetesManifestGenerator.CreateDeployment(output, application, project, deployment)); if (service.Bindings.Count > 0 && project.ManifestInfo?.Service is ServiceManifestInfo k8sService) { // Initialize defaults for service-related settings k8sService.Labels.TryAdd("app.kubernetes.io/name", project.Name); k8sService.Labels.TryAdd("app.kubernetes.io/part-of", application.Name); service.Outputs.Add(KubernetesManifestGenerator.CreateService(output, application, project, deployment, k8sService)); } return(Task.CompletedTask); }
public static ServiceOutput CreateService(OutputContext output, ApplicationBuilder application, ServiceBuilder 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", "Service"); root.Add("apiVersion", "v1"); var metadata = new YamlMappingNode(); root.Add("metadata", metadata); metadata.Add("name", service.Name); var labels = new YamlMappingNode(); metadata.Add("labels", labels); labels.Add("app.kubernetes.io/name", service.Name); labels.Add("app.kubernetes.io/part-of", application.Name); var spec = new YamlMappingNode(); root.Add("spec", spec); var selector = new YamlMappingNode(); spec.Add("selector", selector); selector.Add("app.kubernetes.io/name", service.Name); spec.Add("type", "ClusterIP"); var ports = new YamlSequenceNode(); spec.Add("ports", ports); // We figure out the port based on bindings foreach (var binding in service.Bindings) { if (binding.Protocol == "https") { // We skip https for now in deployment, because the E2E requires certificates // and we haven't done those features yet. continue; } if (binding.Port != null) { var port = new YamlMappingNode(); ports.Add(port); port.Add("name", binding.Name ?? binding.Protocol ?? "http"); port.Add("protocol", "TCP"); // we use assume TCP. YOLO port.Add("port", binding.Port.Value.ToString()); port.Add("targetPort", binding.Port.Value.ToString()); } } return(new KubernetesServiceOutput(service.Name, new YamlDocument(root))); }
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutProject(output, service, out var project)) { return; } if (SkipWithoutContainerInfo(output, service, out var container)) { return; } if (container.UseMultiphaseDockerfile != false) { return; } var outputDirectory = Path.Combine(project.ProjectFile.DirectoryName, "bin", "Release", project.TargetFramework, "publish"); output.WriteDebugLine("Running 'dotnet publish'."); output.WriteCommandLine("dotnet", $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory}\""); var capture = output.Capture(); var exitCode = await Process.ExecuteAsync( $"dotnet", $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory}\"", project.ProjectFile.DirectoryName, stdOut : capture.StdOut, stdErr : capture.StdErr); output.WriteDebugLine($"Done running 'dotnet publish' exit code: {exitCode}"); if (exitCode != 0) { throw new CommandException("'dotnet publish' failed."); } output.WriteInfoLine($"Created Publish Output: '{outputDirectory}'"); service.Outputs.Add(new ProjectPublishOutput(new DirectoryInfo(outputDirectory))); }
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutProject(output, service, out var project)) { return; } if (SkipWithoutContainerInfo(output, service, out var container)) { return; } if (!application.ContainerEngine.IsUsable(out string?unusableReason)) { throw new CommandException($"Cannot generate a docker image for '{service.Name}' because {unusableReason}."); } if (project is DotnetProjectServiceBuilder dotnetProject) { await DockerContainerBuilder.BuildContainerImageAsync(output, application, dotnetProject, container); } else if (project is DockerFileServiceBuilder dockerFile) { await DockerContainerBuilder.BuildContainerImageFromDockerFileAsync(output, application, dockerFile, container); } }
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutProject(output, service, out var project)) { return; } if (SkipWithoutContainerInfo(output, service, out var container)) { return; } if (container.UseMultiphaseDockerfile != false) { return; } // NOTE: we're intentionally not cleaning up here. It's the responsibility of whomever consumes // the publish output to do cleanup. var outputDirectory = TempDirectory.Create(); output.WriteDebugLine("Running 'dotnet publish'."); output.WriteCommandLine("dotnet", $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory.DirectoryPath}\""); var publishResult = await ProcessUtil.RunAsync( $"dotnet", $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory.DirectoryPath}\"", project.ProjectFile.DirectoryName, throwOnError : false); output.WriteDebugLine($"Done running 'dotnet publish' exit code: {publishResult.ExitCode}"); if (publishResult.ExitCode != 0) { outputDirectory.Dispose(); output.WriteInfoLine($"'dotnet publish' failed. Error:"); foreach (var line in publishResult.StandardOutput.Split(Environment.NewLine)) { output.WriteInfoLine(line); } foreach (var line in publishResult.StandardError.Split(Environment.NewLine)) { output.WriteInfoLine(line); } throw new CommandException("'dotnet publish' failed."); } output.WriteDebugLine($"Created Publish Output: '{outputDirectory.DirectoryPath}'"); service.Outputs.Add(new ProjectPublishOutput(outputDirectory.DirectoryInfo)); }
public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutContainerOutput(output, service)) { return(Task.CompletedTask); } if (SkipWithoutProject(output, service, out var project)) { return(Task.CompletedTask); } service.Outputs.Add(KubernetesManifestGenerator.CreateDeployment(output, application, project)); if (service.Bindings.Count > 0) { service.Outputs.Add(KubernetesManifestGenerator.CreateService(output, application, project)); } return(Task.CompletedTask); }
public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutContainerOutput(output, service)) { return(Task.CompletedTask); } if (SkipWithoutProject(output, service, out var project)) { return(Task.CompletedTask); } var component = OamComponentGenerator.CreateOamComponent(output, application, project); service.Outputs.Add(component); return(Task.CompletedTask); }
public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { if (SkipWithoutProject(output, service, out var project)) { return(Task.CompletedTask); } if (SkipWithoutContainerInfo(output, service, out var container)) { return(Task.CompletedTask); } if (project is DotnetProjectServiceBuilder dotnetProject) { DockerfileGenerator.ApplyContainerDefaults(application, dotnetProject, container); } else if (project is DockerFileServiceBuilder dockerFile) { DockerfileGenerator.ApplyContainerDefaults(application, dockerFile, container); } return(Task.CompletedTask); }
public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { // No need to do this computation for a non-project since we're not deploying it. if (!(service is ProjectServiceBuilder project)) { return(Task.CompletedTask); } // Compute ASPNETCORE_URLS based on the bindings exposed by *this* project. foreach (var binding in service.Bindings) { if (binding.Protocol == null && binding.ConnectionString == null) { binding.Protocol = "http"; } if (binding.Port == null && binding.Protocol == "http") { binding.Port = 80; } if (binding.Protocol == "http") { var port = binding.Port ?? 80; var urls = $"http://*{(port == 80 ? "" : (":" + port.ToString()))}"; project.EnvironmentVariables.Add(new EnvironmentVariableBuilder("ASPNETCORE_URLS") { Value = urls, }); project.EnvironmentVariables.Add(new EnvironmentVariableBuilder("PORT") { Value = port.ToString(CultureInfo.InvariantCulture), }); break; } } // Process bindings and turn them into environment variables and secrets. There's // some duplication with the code in m8s (Application.cs) for populating environments. // // service.Service.Bindings is the bindings OUT - this step computes bindings IN. service.Outputs.Add(ComputeBindings(application, service.Dependencies)); foreach (var sidecar in project.Sidecars) { sidecar.Outputs.Add(ComputeBindings(application, sidecar.Dependencies)); } return(Task.CompletedTask); }
public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service) { var yaml = service.Outputs.OfType <IYamlManifestOutput>().ToArray(); if (yaml.Length == 0) { output.WriteDebugLine($"No yaml manifests found for service '{service.Name}'. Skipping."); return(Task.CompletedTask); } var outputFilePath = Path.Combine(OutputDirectory.FullName, $"{service.Name}.yaml"); output.WriteInfoLine($"Writing output to '{outputFilePath}'."); if (File.Exists(outputFilePath) && !Force) { throw new CommandException($"'{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, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), bufferSize: -1, leaveOpen: true); var yamlStream = new YamlStream(yaml.Select(y => y.Yaml)); yamlStream.Save(writer, assignAnchors: false); return(Task.CompletedTask); }