diff --git a/samples/dockercompose/ingress.yml b/samples/dockercompose/ingress.yml index 704b8831..d607c5be 100644 --- a/samples/dockercompose/ingress.yml +++ b/samples/dockercompose/ingress.yml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-basic diff --git a/src/Microsoft.Tye.Core/DeployApplicationKubernetesManifestStep.cs b/src/Microsoft.Tye.Core/DeployApplicationKubernetesManifestStep.cs index a2a7ec9a..68f2259e 100644 --- a/src/Microsoft.Tye.Core/DeployApplicationKubernetesManifestStep.cs +++ b/src/Microsoft.Tye.Core/DeployApplicationKubernetesManifestStep.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.CommandLine.Invocation; using System.IO; using System.Text; @@ -15,7 +16,7 @@ namespace Microsoft.Tye public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application) { - using var step = output.BeginStep(""); + using var step = output.BeginStep("Applying Kubernetes Manifests..."); if (!await KubectlDetector.IsKubectlInstalledAsync(output)) { @@ -59,6 +60,47 @@ namespace Microsoft.Tye } output.WriteInfoLine($"Deployed application '{application.Name}'."); + if (application.Ingress.Count > 0) + { + output.WriteInfoLine($"Waiting for ingress to be deployed. This may take a long time."); + foreach (var ingress in application.Ingress) + { + using var ingressStep = output.BeginStep($"Retrieving details for {ingress.Name}..."); + + var done = false; + + Action complete = line => + { + done = line != "''"; + if (done) + { + output.WriteInfoLine($"IngressIP: {line}"); + } + }; + + var retries = 0; + while (!done && retries < 60) + { + var ingressExitCode = await Process.ExecuteAsync( + "kubectl", + $"get ingress {ingress.Name} -o jsonpath='{{..ip}}'", + Environment.CurrentDirectory, + complete, + capture.StdErr); + + if (ingressExitCode != 0) + { + throw new CommandException("'kubectl get ingress' failed"); + } + + if (!done) + { + await Task.Delay(2000); + retries++; + } + } + } + } } } } diff --git a/src/Microsoft.Tye.Core/KubernetesManifestGenerator.cs b/src/Microsoft.Tye.Core/KubernetesManifestGenerator.cs index e9c27bd5..055c6cec 100644 --- a/src/Microsoft.Tye.Core/KubernetesManifestGenerator.cs +++ b/src/Microsoft.Tye.Core/KubernetesManifestGenerator.cs @@ -20,7 +20,7 @@ namespace Microsoft.Tye var root = new YamlMappingNode(); root.Add("kind", "Ingress"); - root.Add("apiVersion", "extensions/v1beta1"); + root.Add("apiVersion", "networking.k8s.io/v1"); var metadata = new YamlMappingNode(); root.Add("metadata", metadata); @@ -74,7 +74,10 @@ namespace Microsoft.Tye var backend = new YamlMappingNode(); path.Add("backend", backend); - backend.Add("serviceName", ingressRule.Service); + var backendService = new YamlMappingNode(); + backend.Add("service", backendService); + + backendService.Add("name", ingressRule.Service); var service = application.Services.FirstOrDefault(s => s.Name == ingressRule.Service); if (service is null) @@ -88,7 +91,11 @@ namespace Microsoft.Tye throw new InvalidOperationException($"Could not resolve an http binding for service '{service.Name}'."); } - backend.Add("servicePort", (binding.Port ?? 80).ToString(CultureInfo.InvariantCulture)); + var backendPort = new YamlMappingNode(); + + backendService.Add("port", backendPort); + + backendPort.Add("number", (binding.Port ?? 80).ToString(CultureInfo.InvariantCulture)); // Tye implements path matching similar to this example: // https://kubernetes.github.io/ingress-nginx/examples/rewrite/ @@ -111,6 +118,9 @@ namespace Microsoft.Tye path.Add("path", regex); } } + + // Only support prefix matching for now + path.Add("pathType", "Prefix"); } } diff --git a/src/Microsoft.Tye.Core/ValidateIngressStep.cs b/src/Microsoft.Tye.Core/ValidateIngressStep.cs index 70a4a1cc..55e20c86 100644 --- a/src/Microsoft.Tye.Core/ValidateIngressStep.cs +++ b/src/Microsoft.Tye.Core/ValidateIngressStep.cs @@ -140,21 +140,20 @@ namespace Microsoft.Tye else { // If we get here then we should deploy the ingress controller. + + // For some reason, the first time we apply the ingress controller, an exception is thrown + // saying "ingress-nginx-admission-create" is invalid. Therefore, we are going to blindly assume the + // controller successfully ran. + output.WriteDebugLine($"Running 'kubectl apply'"); output.WriteCommandLine("kubectl", $"apply -f \"https://aka.ms/tye/ingress/deploy\""); var capture = output.Capture(); var exitCode = await Process.ExecuteAsync( $"kubectl", $"apply -f \"https://aka.ms/tye/ingress/deploy\"", - System.Environment.CurrentDirectory, - stdOut: capture.StdOut, - stdErr: capture.StdErr); + System.Environment.CurrentDirectory); output.WriteDebugLine($"Done running 'kubectl apply' exit code: {exitCode}"); - if (exitCode != 0) - { - throw new CommandException("'kubectl apply' failed."); - } output.WriteInfoLine($"Waiting for ingress-nginx controller to start."); @@ -162,7 +161,7 @@ namespace Microsoft.Tye // after creating the controller will fail if the webhook isn't ready. // // Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io": - // Post https://ingress-nginx-controller-admission.ingress-nginx.svc:443/extensions/v1beta1/ingresses?timeout=30s: + // Post https://ingress-nginx-controller-admission.ingress-nginx.svc:443/networking.k8s.io/v1/ingresses?timeout=30s: // dial tcp 10.0.31.130:443: connect: connection refused // // Unfortunately this is the likely case for us. diff --git a/test/E2ETest/testassets/generate/apps-with-ingress.yaml b/test/E2ETest/testassets/generate/apps-with-ingress.yaml index 1739d308..e8cbbe05 100644 --- a/test/E2ETest/testassets/generate/apps-with-ingress.yaml +++ b/test/E2ETest/testassets/generate/apps-with-ingress.yaml @@ -125,7 +125,7 @@ spec: ... --- kind: Ingress -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 metadata: name: ingress annotations: @@ -138,25 +138,37 @@ spec: - http: paths: - backend: - serviceName: appa - servicePort: 80 + service: + name: appa + port: + number: 80 path: /A(/|$)(.*) + pathType: Prefix - backend: - serviceName: appb - servicePort: 80 + service: + name: appb + port: + number: 80 path: /B(/|$)(.*) + pathType: Prefix - host: a.example.com http: paths: - backend: - serviceName: appa - servicePort: 80 + service: + name: appa + port: + number: 80 path: /()(.*) + pathType: Prefix - host: b.example.com http: paths: - backend: - serviceName: appb - servicePort: 80 + service: + name: appb + port: + number: 80 path: /()(.*) + pathType: Prefix ...