diff --git a/src/Microsoft.Tye.Core/KubernetesManifestGenerator.cs b/src/Microsoft.Tye.Core/KubernetesManifestGenerator.cs index f9f284de..e9c27bd5 100644 --- a/src/Microsoft.Tye.Core/KubernetesManifestGenerator.cs +++ b/src/Microsoft.Tye.Core/KubernetesManifestGenerator.cs @@ -42,71 +42,73 @@ namespace Microsoft.Tye var spec = new YamlMappingNode(); root.Add("spec", spec); - if (ingress.Rules.Count > 0) + if (ingress.Rules.Count == 0) { - var rules = new YamlSequenceNode(); - spec.Add("rules", rules); + return new KubernetesIngressOutput(ingress.Name, new YamlDocument(root)); + } - // k8s ingress is grouped by host first, then grouped by path - foreach (var hostgroup in ingress.Rules.GroupBy(r => r.Host)) - { - var rule = new YamlMappingNode(); - rules.Add(rule); + var rules = new YamlSequenceNode(); + spec.Add("rules", rules); - if (!string.IsNullOrEmpty(hostgroup.Key)) - { - rule.Add("host", hostgroup.Key); - } + // k8s ingress is grouped by host first, then grouped by path + foreach (var hostgroup in ingress.Rules.GroupBy(r => r.Host)) + { + var rule = new YamlMappingNode(); + rules.Add(rule); - var http = new YamlMappingNode(); - rule.Add("http", http); + if (!string.IsNullOrEmpty(hostgroup.Key)) + { + rule.Add("host", hostgroup.Key); + } - var paths = new YamlSequenceNode(); - http.Add("paths", paths); + var http = new YamlMappingNode(); + rule.Add("http", http); - foreach (var ingressRule in hostgroup) - { - var path = new YamlMappingNode(); - paths.Add(path); + var paths = new YamlSequenceNode(); + http.Add("paths", paths); - var backend = new YamlMappingNode(); - path.Add("backend", backend); - backend.Add("serviceName", ingressRule.Service); + foreach (var ingressRule in hostgroup) + { + var path = new YamlMappingNode(); + paths.Add(path); - var service = application.Services.FirstOrDefault(s => s.Name == ingressRule.Service); - if (service is null) - { - throw new InvalidOperationException($"Could not resolve service '{ingressRule.Service}'."); - } + var backend = new YamlMappingNode(); + path.Add("backend", backend); + backend.Add("serviceName", ingressRule.Service); - var binding = service.Bindings.FirstOrDefault(b => b.Name is null || b.Name == "http"); - if (binding is null) - { - throw new InvalidOperationException($"Could not resolve an http binding for service '{service.Name}'."); - } + var service = application.Services.FirstOrDefault(s => s.Name == ingressRule.Service); + if (service is null) + { + throw new InvalidOperationException($"Could not resolve service '{ingressRule.Service}'."); + } - backend.Add("servicePort", (binding.Port ?? 80).ToString(CultureInfo.InvariantCulture)); + var binding = service.Bindings.FirstOrDefault(b => b.Name is null || b.Name == "http"); + if (binding is null) + { + throw new InvalidOperationException($"Could not resolve an http binding for service '{service.Name}'."); + } - // Tye implements path matching similar to this example: - // https://kubernetes.github.io/ingress-nginx/examples/rewrite/ - // - // Therefore our rewrite-target is set to $2 - we want to make sure we have - // two capture groups. - if (string.IsNullOrEmpty(ingressRule.Path) || ingressRule.Path == "/") + backend.Add("servicePort", (binding.Port ?? 80).ToString(CultureInfo.InvariantCulture)); + + // Tye implements path matching similar to this example: + // https://kubernetes.github.io/ingress-nginx/examples/rewrite/ + // + // Therefore our rewrite-target is set to $2 - we want to make sure we have + // two capture groups. + if (string.IsNullOrEmpty(ingressRule.Path) || ingressRule.Path == "/") + { + path.Add("path", "/()(.*)"); // () is an empty capture group. + } + else + { + if (ingressRule.PreservePath) { - path.Add("path", "/()(.*)"); // () is an empty capture group. + path.Add("path", $"/()({ingressRule.Path.Trim('/')}.*)"); } else { - if (ingressRule.PreservePath) - { - path.Add("path", $"/()({ingressRule.Path.Trim('/')}.*)"); - } - else - { - var regex = $"{ingressRule.Path.TrimEnd('/')}(/|$)(.*)"; - path.Add("path", regex); - } + var regex = $"{ingressRule.Path.TrimEnd('/')}(/|$)(.*)"; + path.Add("path", regex); } } } @@ -182,16 +184,18 @@ namespace Microsoft.Tye continue; } - if (binding.Port != null) + 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.ContainerPort ?? binding.Port.Value).ToString()); + continue; } + + 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.ContainerPort ?? binding.Port.Value).ToString()); } return new KubernetesServiceOutput(project.Name, new YamlDocument(root)); @@ -205,10 +209,11 @@ namespace Microsoft.Tye { var bindings = project.Outputs.OfType().FirstOrDefault(); - var root = new YamlMappingNode(); - - root.Add("kind", "Deployment"); - root.Add("apiVersion", "apps/v1"); + var root = new YamlMappingNode + { + {"kind", "Deployment"}, + {"apiVersion", "apps/v1"} + }; var metadata = new YamlMappingNode(); root.Add("metadata", metadata); @@ -343,7 +348,7 @@ namespace Microsoft.Tye }); } - if (bindings is object) + if (bindings != null) { AddEnvironmentVariablesForComputedBindings(env, bindings); } @@ -377,12 +382,14 @@ namespace Microsoft.Tye continue; } - if (binding.Port != null) + if (binding.Port == null) { - var containerPort = new YamlMappingNode(); - ports.Add(containerPort); - containerPort.Add("containerPort", (binding.ContainerPort ?? binding.Port.Value).ToString()); + continue; } + + var containerPort = new YamlMappingNode(); + ports.Add(containerPort); + containerPort.Add("containerPort", (binding.ContainerPort ?? binding.Port.Value).ToString()); } } @@ -431,48 +438,51 @@ namespace Microsoft.Tye }); } - if (sidecarBindings is object) + if (sidecarBindings != null) { AddEnvironmentVariablesForComputedBindings(env, sidecarBindings); } } - if (project.RelocateDiagnosticsDomainSockets) + if (!project.RelocateDiagnosticsDomainSockets) { - // 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"); + 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) + if (!project.RelocateDiagnosticsDomainSockets) { - // 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)); } + // 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)); } - private static void AddProbe(ServiceBuilder service, YamlMappingNode container, ProbeBuilder builder, string name) + private static void AddProbe(ServiceBuilder service, YamlMappingNode container, ProbeBuilder builder, string probeName) { var probe = new YamlMappingNode(); - container.Add(name, probe); + container.Add(probeName, probe); if (builder.Http != null) { @@ -511,11 +521,13 @@ namespace Microsoft.Tye var headers = new YamlSequenceNode(); httpGet.Add("httpHeaders", headers); - foreach (var builderHeader in builderHttp.Headers) + foreach (var (name, value) in builderHttp.Headers) { - var header = new YamlMappingNode(); - header.Add("name", builderHeader.Key); - header.Add("value", builderHeader.Value.ToString()!); + var header = new YamlMappingNode + { + {"name", name}, + {"value", value.ToString()!} + }; headers.Add(header); } } @@ -531,7 +543,7 @@ namespace Microsoft.Tye { foreach (var binding in bindings.Bindings.OfType()) { - env.Add(new YamlMappingNode() + env.Add(new YamlMappingNode { { "name", binding.Name }, { "value", new YamlScalarNode(binding.Value) { Style = ScalarStyle.SingleQuoted, } }, @@ -546,16 +558,16 @@ namespace Microsoft.Tye // name: mysecret // key: username - if (binding is SecretConnectionStringInputBinding connectionStringBinding) - { - AddSecret(env, connectionStringBinding.KeyName, binding.Name, "connectionstring"); - - } - else if (binding is SecretUrlInputBinding urlBinding) + switch (binding) { - AddSecret(env, $"{urlBinding.KeyNameBase}__PROTOCOL", binding.Name, "protocol"); - AddSecret(env, $"{urlBinding.KeyNameBase}__HOST", binding.Name, "host"); - AddSecret(env, $"{urlBinding.KeyNameBase}__PORT", binding.Name, "port"); + case SecretConnectionStringInputBinding connectionStringBinding: + AddSecret(env, connectionStringBinding.KeyName, binding.Name, "connectionstring"); + break; + case SecretUrlInputBinding urlBinding: + AddSecret(env, $"{urlBinding.KeyNameBase}__PROTOCOL", binding.Name, "protocol"); + AddSecret(env, $"{urlBinding.KeyNameBase}__HOST", binding.Name, "host"); + AddSecret(env, $"{urlBinding.KeyNameBase}__PORT", binding.Name, "port"); + break; } }