public static KubernetesServiceOutput CreateService( OutputContext output, ApplicationBuilder application, ProjectServiceBuilder project, DeploymentManifestInfo deployment, ServiceManifestInfo service) { var root = new YamlMappingNode(); root.Add("kind", "Service"); root.Add("apiVersion", "v1"); var metadata = new YamlMappingNode(); root.Add("metadata", metadata); metadata.Add("name", project.Name); if (!string.IsNullOrEmpty(application.Namespace)) { metadata.Add("namespace", application.Namespace); } if (service.Annotations.Count > 0) { var annotations = new YamlMappingNode(); metadata.Add("annotations", annotations); foreach (var annotation in service.Annotations) { annotations.Add(annotation.Key, new YamlScalarNode(annotation.Value) { Style = ScalarStyle.SingleQuoted, }); } } var labels = new YamlMappingNode(); metadata.Add("labels", labels); foreach (var label in service.Labels) { labels.Add(label.Key, new YamlScalarNode(label.Value) { Style = ScalarStyle.SingleQuoted, }); } var spec = new YamlMappingNode(); root.Add("spec", spec); var selector = new YamlMappingNode(); spec.Add("selector", selector); // We need the name so we can use it with selector. if (!deployment.Labels.TryGetValue("app.kubernetes.io/name", out var selectorLabelValue)) { throw new InvalidOperationException("The label 'app.kubernetes.io/name` is required."); } selector.Add("app.kubernetes.io/name", selectorLabelValue); spec.Add("type", "ClusterIP"); var ports = new YamlSequenceNode(); spec.Add("ports", ports); // We figure out the port based on bindings foreach (var binding in project.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(project.Name, new YamlDocument(root))); }
public static KubernetesDeploymentOutput CreateDeployment( OutputContext output, ApplicationBuilder application, ProjectServiceBuilder project, DeploymentManifestInfo deployment) { var bindings = project.Outputs.OfType <ComputedBindings>().FirstOrDefault(); var root = new YamlMappingNode(); root.Add("kind", "Deployment"); root.Add("apiVersion", "apps/v1"); var metadata = new YamlMappingNode(); root.Add("metadata", metadata); metadata.Add("name", project.Name); if (!string.IsNullOrEmpty(application.Namespace)) { metadata.Add("namespace", application.Namespace); } if (deployment.Annotations.Count > 0) { var annotations = new YamlMappingNode(); metadata.Add("annotations", annotations); foreach (var annotation in deployment.Annotations) { annotations.Add(annotation.Key, new YamlScalarNode(annotation.Value) { Style = ScalarStyle.SingleQuoted, }); } } var labels = new YamlMappingNode(); metadata.Add("labels", labels); foreach (var label in deployment.Labels) { labels.Add(label.Key, new YamlScalarNode(label.Value) { Style = ScalarStyle.SingleQuoted, }); } var spec = new YamlMappingNode(); root.Add("spec", spec); spec.Add("replicas", project.Replicas.ToString()); var selector = new YamlMappingNode(); spec.Add("selector", selector); var matchLabels = new YamlMappingNode(); selector.Add("matchLabels", matchLabels); // We need the name so we can use it with matchLabels. if (!deployment.Labels.TryGetValue("app.kubernetes.io/name", out var matchLabelsLabelValue)) { throw new InvalidOperationException("The label 'app.kubernetes.io/name` is required."); } matchLabels.Add("app.kubernetes.io/name", matchLabelsLabelValue); var template = new YamlMappingNode(); spec.Add("template", template); metadata = new YamlMappingNode(); template.Add("metadata", metadata); if (deployment.Annotations.Count > 0) { var annotations = new YamlMappingNode(); metadata.Add("annotations", annotations); foreach (var annotation in deployment.Annotations) { annotations.Add(annotation.Key, new YamlScalarNode(annotation.Value) { Style = ScalarStyle.SingleQuoted, }); } } labels = new YamlMappingNode(); metadata.Add("labels", labels); foreach (var label in deployment.Labels) { labels.Add(label.Key, new YamlScalarNode(label.Value) { Style = ScalarStyle.SingleQuoted, }); } spec = new YamlMappingNode(); template.Add("spec", spec); var containers = new YamlSequenceNode(); spec.Add("containers", containers); var images = project.Outputs.OfType <DockerImageOutput>(); foreach (var image in images) { var container = new YamlMappingNode(); containers.Add(container); container.Add("name", project.Name); // NOTE: to really support multiple images we'd need to generate unique names. container.Add("image", $"{image.ImageName}:{image.ImageTag}"); container.Add("imagePullPolicy", "Always"); // helps avoid problems with development + weak versioning if (project.EnvironmentVariables.Count > 0 || // We generate ASPNETCORE_URLS if there are bindings for http project.Bindings.Any(b => b.Protocol == "http" || b.Protocol is null) || // We generate environment variables for other services if there dependencies (bindings is object && bindings.Bindings.Any())) { var env = new YamlSequenceNode(); container.Add("env", env); foreach (var kvp in project.EnvironmentVariables) { env.Add(new YamlMappingNode() { { "name", kvp.Name }, { "value", new YamlScalarNode(kvp.Value) { Style = ScalarStyle.SingleQuoted, } }, }); } if (bindings is object) { foreach (var binding in bindings.Bindings.OfType <EnvironmentVariableInputBinding>()) { env.Add(new YamlMappingNode() { { "name", binding.Name }, { "value", new YamlScalarNode(binding.Value) { Style = ScalarStyle.SingleQuoted, } }, }); } foreach (var binding in bindings.Bindings.OfType <SecretInputBinding>()) { //- name: SECRET_USERNAME // valueFrom: // secretKeyRef: // name: mysecret // key: username if (binding is SecretConnectionStringInputBinding connectionStringBinding) { env.Add(new YamlMappingNode() { { "name", connectionStringBinding.KeyName }, { "valueFrom", new YamlMappingNode() { { "secretKeyRef", new YamlMappingNode() { { "name", new YamlScalarNode(binding.Name) { Style = ScalarStyle.SingleQuoted } }, { "key", new YamlScalarNode("connectionstring") { Style = ScalarStyle.SingleQuoted } }, } }, } }, }); } else if (binding is SecretUrlInputBinding urlBinding) { env.Add(new YamlMappingNode() { { "name", $"{urlBinding.KeyNameBase}__PROTOCOL" }, { "valueFrom", new YamlMappingNode() { { "secretKeyRef", new YamlMappingNode() { { "name", new YamlScalarNode(binding.Name) { Style = ScalarStyle.SingleQuoted } }, { "key", new YamlScalarNode("protocol") { Style = ScalarStyle.SingleQuoted } }, } }, } }, }); env.Add(new YamlMappingNode() { { "name", $"{urlBinding.KeyNameBase}__HOST" }, { "valueFrom", new YamlMappingNode() { { "secretKeyRef", new YamlMappingNode() { { "name", new YamlScalarNode(binding.Name) { Style = ScalarStyle.SingleQuoted } }, { "key", new YamlScalarNode("host") { Style = ScalarStyle.SingleQuoted } }, } }, } }, }); env.Add(new YamlMappingNode() { { "name", $"{urlBinding.KeyNameBase}__PORT" }, { "valueFrom", new YamlMappingNode() { { "secretKeyRef", new YamlMappingNode() { { "name", new YamlScalarNode(binding.Name) { Style = ScalarStyle.SingleQuoted } }, { "key", new YamlScalarNode("port") { Style = ScalarStyle.SingleQuoted } }, } }, } }, }); } } } } if (project.Bindings.Count > 0) { var ports = new YamlSequenceNode(); container.Add("ports", ports); foreach (var binding in project.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 containerPort = new YamlMappingNode(); ports.Add(containerPort); containerPort.Add("containerPort", binding.Port.Value.ToString()); } } } } return(new KubernetesDeploymentOutput(project.Name, new YamlDocument(root))); }
public static KubernetesDeploymentOutput CreateDeployment( OutputContext output, ApplicationBuilder application, ProjectServiceBuilder project, DeploymentManifestInfo deployment) { var bindings = project.Outputs.OfType <ComputedBindings>().FirstOrDefault(); var root = new YamlMappingNode { { "kind", "Deployment" }, { "apiVersion", "apps/v1" } }; var metadata = new YamlMappingNode(); root.Add("metadata", metadata); metadata.Add("name", project.Name); if (!string.IsNullOrEmpty(application.Namespace)) { metadata.Add("namespace", application.Namespace); } if (deployment.Annotations.Count > 0) { var annotations = new YamlMappingNode(); metadata.Add("annotations", annotations); foreach (var annotation in deployment.Annotations) { annotations.Add(annotation.Key, new YamlScalarNode(annotation.Value) { Style = ScalarStyle.SingleQuoted, }); } } var labels = new YamlMappingNode(); metadata.Add("labels", labels); foreach (var label in deployment.Labels) { labels.Add(label.Key, new YamlScalarNode(label.Value) { Style = ScalarStyle.SingleQuoted, }); } var spec = new YamlMappingNode(); root.Add("spec", spec); spec.Add("replicas", project.Replicas.ToString()); var selector = new YamlMappingNode(); spec.Add("selector", selector); var matchLabels = new YamlMappingNode(); selector.Add("matchLabels", matchLabels); // We need the name so we can use it with matchLabels. if (!deployment.Labels.TryGetValue("app.kubernetes.io/name", out var matchLabelsLabelValue)) { throw new InvalidOperationException("The label 'app.kubernetes.io/name` is required."); } matchLabels.Add("app.kubernetes.io/name", matchLabelsLabelValue); var template = new YamlMappingNode(); spec.Add("template", template); metadata = new YamlMappingNode(); template.Add("metadata", metadata); if (deployment.Annotations.Count > 0) { var annotations = new YamlMappingNode(); metadata.Add("annotations", annotations); foreach (var annotation in deployment.Annotations) { annotations.Add(annotation.Key, new YamlScalarNode(annotation.Value) { Style = ScalarStyle.SingleQuoted, }); } } labels = new YamlMappingNode(); metadata.Add("labels", labels); foreach (var label in deployment.Labels) { labels.Add(label.Key, new YamlScalarNode(label.Value) { Style = ScalarStyle.SingleQuoted, }); } spec = new YamlMappingNode(); template.Add("spec", spec); if (project.Sidecars.Count > 0) { // Share process namespace when we have sidecars. So we can list other processes. // see: https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/#understanding-process-namespace-sharing spec.Add("shareProcessNamespace", new YamlScalarNode("true") { Style = ScalarStyle.Plain }); } if (project.RelocateDiagnosticsDomainSockets) { // Our diagnostics functionality uses $TMPDIR to locate other dotnet processes through // eventpipe. see: https://github.com/dotnet/diagnostics/blob/master/documentation/design-docs/ipc-protocol.md#transport // // In order for diagnostics features to 'find' each other, we need to make $TMPDIR into // something shared. // // see: https://kubernetes.io/docs/tasks/access-application-cluster/communicate-containers-same-pod-shared-volume/ project.EnvironmentVariables.Add(new EnvironmentVariableBuilder("TMPDIR") { Value = "/var/tye/diagnostics", }); foreach (var sidecar in project.Sidecars) { sidecar.EnvironmentVariables.Add(new EnvironmentVariableBuilder("TMPDIR") { Value = "/var/tye/diagnostics", }); } } var containers = new YamlSequenceNode(); spec.Add("containers", containers); var images = project.Outputs.OfType <DockerImageOutput>(); foreach (var image in images) { var container = new YamlMappingNode(); containers.Add(container); container.Add("name", project.Name); // NOTE: to really support multiple images we'd need to generate unique names. container.Add("image", $"{image.ImageName}:{image.ImageTag}"); container.Add("imagePullPolicy", "Always"); // helps avoid problems with development + weak versioning var volumeMounts = new YamlSequenceNode(); var localMounts = new YamlSequenceNode(); if (project.Volumes.Count > 0) { container.Add("volumeMounts", volumeMounts); spec.Add("volumes", localMounts); foreach (var vol in project.Volumes) { var volumeMount = new YamlMappingNode(); volumeMounts.Add(volumeMount); volumeMount.Add("name", vol.Name); volumeMount.Add("mountPath", vol.Target); var localMount = new YamlMappingNode(); var hostPath = new YamlMappingNode(); var path = new YamlMappingNode(); localMounts.Add(localMount); localMount.Add("name", vol.Name); localMount.Add("hostPath", hostPath); hostPath.Add("path", vol.Source); } } if (project.EnvironmentVariables.Count > 0 || // We generate ASPNETCORE_URLS if there are bindings for http project.Bindings.Any(b => b.Protocol == "http" || b.Protocol is null) || // We generate environment variables for other services if there dependencies bindings?.Bindings.Count > 0) { var env = new YamlSequenceNode(); container.Add("env", env); foreach (var kvp in project.EnvironmentVariables) { env.Add(new YamlMappingNode() { { "name", kvp.Name }, { "value", new YamlScalarNode(kvp.Value) { Style = ScalarStyle.SingleQuoted, } }, }); } if (bindings != null) { AddEnvironmentVariablesForComputedBindings(env, bindings); } if (project.RelocateDiagnosticsDomainSockets) { // volumeMounts: // - name: shared-data // mountPath: /usr/share/nginx/html if (!container.Children.ContainsKey("volumeMounts")) { container.Add("volumeMounts", volumeMounts); } var volumeMount = new YamlMappingNode(); volumeMounts.Add(volumeMount); volumeMount.Add("name", "tye-diagnostics"); volumeMount.Add("mountPath", "/var/tye/diagnostics"); } } if (project.Bindings.Count > 0) { var ports = new YamlSequenceNode(); container.Add("ports", ports); foreach (var binding in project.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) { continue; } var containerPort = new YamlMappingNode(); ports.Add(containerPort); containerPort.Add("containerPort", (binding.ContainerPort ?? binding.Port.Value).ToString()); } } if (project.Liveness != null) { AddProbe(project, container, project.Liveness !, "livenessProbe"); } if (project.Readiness != null) { AddProbe(project, container, project.Readiness !, "readinessProbe"); } } foreach (var sidecar in project.Sidecars) { var container = new YamlMappingNode(); containers.Add(container); container.Add("name", sidecar.Name); // NOTE: to really support multiple images we'd need to generate unique names. container.Add("image", $"{sidecar.ImageName}:{sidecar.ImageTag}"); container.Add("imagePullPolicy", "Always"); // helps avoid problems with development + weak versioning if (sidecar.Args.Count > 0) { var args = new YamlSequenceNode(); container.Add("args", args); foreach (var arg in sidecar.Args) { args.Add(new YamlScalarNode(arg) { Style = ScalarStyle.SingleQuoted, }); } } var sidecarBindings = sidecar.Outputs.OfType <ComputedBindings>().FirstOrDefault(); if (sidecar.EnvironmentVariables.Count > 0 || sidecarBindings?.Bindings.Count > 0) { var env = new YamlSequenceNode(); container.Add("env", env); foreach (var kvp in sidecar.EnvironmentVariables) { env.Add(new YamlMappingNode() { { "name", kvp.Name }, { "value", new YamlScalarNode(kvp.Value) { Style = ScalarStyle.SingleQuoted, } }, }); } if (sidecarBindings != null) { AddEnvironmentVariablesForComputedBindings(env, sidecarBindings); } } if (!project.RelocateDiagnosticsDomainSockets) { continue; } // volumeMounts: // - name: shared-data // mountPath: /usr/share/nginx/html var volumeMounts = new YamlSequenceNode(); container.Add("volumeMounts", volumeMounts); var volumeMount = new YamlMappingNode(); volumeMounts.Add(volumeMount); volumeMount.Add("name", "tye-diagnostics"); volumeMount.Add("mountPath", "/var/tye/diagnostics"); } if (!project.RelocateDiagnosticsDomainSockets) { return(new KubernetesDeploymentOutput(project.Name, new YamlDocument(root))); } // volumes: // - name: shared-data // emptyDir: {} var volumes = new YamlSequenceNode(); spec.Add("volumes", volumes); var volume = new YamlMappingNode(); volumes.Add(volume); volume.Add("name", "tye-diagnostics"); volume.Add("emptyDir", new YamlMappingNode()); return(new KubernetesDeploymentOutput(project.Name, new YamlDocument(root))); }