Browse Source

Revert ingress update for 1.18 or lower (#893)

pull/897/head
Justin Kotalik 5 years ago
committed by GitHub
parent
commit
ae44f39542
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Microsoft.Tye.Core/DeployApplicationKubernetesManifestStep.cs
  2. 5
      src/Microsoft.Tye.Core/GenerateIngressKubernetesManifestStep.cs
  3. 40
      src/Microsoft.Tye.Core/KubernetesManifestGenerator.cs
  4. 9
      src/Microsoft.Tye.Core/ValidateIngressStep.cs
  5. 28
      src/shared/KubectlDetector.cs
  6. 2
      src/tye/Program.DeployCommand.cs
  7. 3
      test/E2ETest/Microsoft.Tye.E2ETests.csproj
  8. 12
      test/E2ETest/TyeGenerateTests.cs
  9. 162
      test/E2ETest/testassets/generate/apps-with-ingress.1.18.yaml
  10. 0
      test/E2ETest/testassets/generate/apps-with-ingress.1.19.yaml

2
src/Microsoft.Tye.Core/DeployApplicationKubernetesManifestStep.cs

@ -18,7 +18,7 @@ namespace Microsoft.Tye
{
using var step = output.BeginStep("Applying Kubernetes Manifests...");
if (!await KubectlDetector.IsKubectlInstalledAsync(output))
if (await KubectlDetector.GetKubernetesServerVersion(output) == null)
{
throw new CommandException($"Cannot apply manifests because kubectl is not installed.");
}

5
src/Microsoft.Tye.Core/GenerateIngressKubernetesManifestStep.cs

@ -12,10 +12,9 @@ namespace Microsoft.Tye
public string Environment { get; set; } = "production";
public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, IngressBuilder ingress)
public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, IngressBuilder ingress)
{
ingress.Outputs.Add(KubernetesManifestGenerator.CreateIngress(output, application, ingress));
return Task.CompletedTask;
ingress.Outputs.Add(await KubernetesManifestGenerator.CreateIngress(output, application, ingress));
}
}
}

40
src/Microsoft.Tye.Core/KubernetesManifestGenerator.cs

@ -5,6 +5,7 @@
using System;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
@ -12,15 +13,25 @@ namespace Microsoft.Tye
{
internal static class KubernetesManifestGenerator
{
public static KubernetesIngressOutput CreateIngress(
private static System.Version MinIngressVersion = new System.Version(1, 19);
public static async Task<KubernetesIngressOutput> CreateIngress(
OutputContext output,
ApplicationBuilder application,
IngressBuilder ingress)
{
var root = new YamlMappingNode();
var k8sVersion = await KubectlDetector.GetKubernetesServerVersion(output);
root.Add("kind", "Ingress");
root.Add("apiVersion", "networking.k8s.io/v1");
if (k8sVersion >= MinIngressVersion)
{
root.Add("apiVersion", "networking.k8s.io/v1");
}
else
{
root.Add("apiVersion", "extensions/v1beta1");
}
var metadata = new YamlMappingNode();
root.Add("metadata", metadata);
@ -74,10 +85,6 @@ namespace Microsoft.Tye
var backend = new YamlMappingNode();
path.Add("backend", backend);
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)
@ -91,11 +98,25 @@ namespace Microsoft.Tye
throw new InvalidOperationException($"Could not resolve an http binding for service '{service.Name}'.");
}
var backendPort = new YamlMappingNode();
if (k8sVersion >= MinIngressVersion)
{
var backendService = new YamlMappingNode();
backend.Add("service", backendService);
backendService.Add("port", backendPort);
backendService.Add("name", ingressRule.Service);
backendPort.Add("number", (binding.Port ?? 80).ToString(CultureInfo.InvariantCulture));
var backendPort = new YamlMappingNode();
backendService.Add("port", backendPort);
backendPort.Add("number", (binding.Port ?? 80).ToString(CultureInfo.InvariantCulture));
path.Add("pathType", "Prefix");
}
else
{
backend.Add("serviceName", ingressRule.Service);
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/
@ -120,7 +141,6 @@ namespace Microsoft.Tye
}
// Only support prefix matching for now
path.Add("pathType", "Prefix");
}
}

9
src/Microsoft.Tye.Core/ValidateIngressStep.cs

@ -43,7 +43,7 @@ namespace Microsoft.Tye
return;
}
if (!await KubectlDetector.IsKubectlInstalledAsync(output))
if (await KubectlDetector.GetKubernetesServerVersion(output) == null)
{
throw new CommandException($"Cannot validate ingress because kubectl is not installed.");
}
@ -141,9 +141,10 @@ namespace Microsoft.Tye
{
// 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.
// The first time we apply the ingress controller, the validating webhook will not have started.
// This causes an error to be returned from the process. As this always happens, we are going to
// not check the error returned and assume the kubectl command worked. This is double checked in
// the future as well when we try to create the ingress resource.
output.WriteDebugLine($"Running 'kubectl apply'");
output.WriteCommandLine("kubectl", $"apply -f \"https://aka.ms/tye/ingress/deploy\"");

28
src/shared/KubectlDetector.cs

@ -3,16 +3,17 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Text.Json;
using System.Threading.Tasks;
namespace Microsoft.Tye
{
internal static class KubectlDetector
{
private static Lazy<Task<bool>> _kubectlInstalled = new Lazy<Task<bool>>(DetectKubectlInstalled);
private static Lazy<Task<Version?>> _kubectlInstalled = new Lazy<Task<Version?>>(GetKubectlVersion);
private static Lazy<Task<bool>> _kubectlConnectedToCluster = new Lazy<Task<bool>>(DetectKubectlConnectedToCluster);
public static Task<bool> IsKubectlInstalledAsync(OutputContext output)
public static Task<Version?> GetKubernetesServerVersion(OutputContext output)
{
if (!_kubectlInstalled.IsValueCreated)
{
@ -30,19 +31,34 @@ namespace Microsoft.Tye
return _kubectlConnectedToCluster.Value;
}
private static async Task<bool> DetectKubectlInstalled()
private static async Task<Version?> GetKubectlVersion()
{
try
{
// Ignoring the exit code and relying on Process to throw if kubectl is not found
// kubectl version will return non-zero if you're not connected to a cluster.
await ProcessUtil.RunAsync("kubectl", "version", throwOnError: false);
return true;
var result = await ProcessUtil.RunAsync("kubectl", "version -o json", throwOnError: false);
var output = result.StandardOutput;
using var jsonDoc = JsonDocument.Parse(output);
foreach (JsonProperty element in jsonDoc.RootElement.EnumerateObject())
{
if (element.Name != "serverVersion")
{
continue;
}
var major = int.Parse(element.Value.GetProperty("major").GetString());
var minor = int.Parse(element.Value.GetProperty("minor").GetString());
var version = new Version(major, minor);
return version;
}
return null;
}
catch (Exception)
{
// Unfortunately, process throws
return false;
return null;
}
}

2
src/tye/Program.DeployCommand.cs

@ -64,7 +64,7 @@ namespace Microsoft.Tye
{
var watch = System.Diagnostics.Stopwatch.StartNew();
if (!await KubectlDetector.IsKubectlInstalledAsync(output))
if (await KubectlDetector.GetKubernetesServerVersion(output) == null)
{
throw new CommandException($"Cannot apply manifests because kubectl is not installed.");
}

3
test/E2ETest/Microsoft.Tye.E2ETests.csproj

@ -24,11 +24,14 @@
<ProjectReference Include="..\Test.Infrastructure\Test.Infrastructure.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Tye.Core\Microsoft.Tye.Core.csproj" />
<ProjectReference Include="..\..\src\tye\tye.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="testassets\**\*" CopyToOutputDirectory="PreserveNewest" />
<Compile Remove="testassets\**\*" />
<None Remove="testassets\generate\apps-with-ingress.1.18.yaml" />
<Compile Include="..\..\src\shared\KubectlDetector.cs" Link="KubectlDetector.cs" />
</ItemGroup>
<ItemGroup>

12
test/E2ETest/TyeGenerateTests.cs

@ -379,9 +379,17 @@ namespace E2ETest
// name of application is the folder
var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{applicationName}-generate-{environment}.yaml"));
var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{applicationName}.yaml");
YamlAssert.Equals(expectedContent, content, output);
if (await KubectlDetector.GetKubernetesServerVersion(outputContext) >= new Version(1, 19))
{
var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{applicationName}.1.19.yaml");
YamlAssert.Equals(expectedContent, content, output);
}
else
{
var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{applicationName}.1.18.yaml");
YamlAssert.Equals(expectedContent, content, output);
}
await DockerAssert.AssertImageExistsAsync(output, "appa");
await DockerAssert.AssertImageExistsAsync(output, "appb");

162
test/E2ETest/testassets/generate/apps-with-ingress.1.18.yaml

@ -0,0 +1,162 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: appa
labels:
app.kubernetes.io/name: 'appa'
app.kubernetes.io/part-of: 'apps-with-ingress'
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: appa
template:
metadata:
labels:
app.kubernetes.io/name: 'appa'
app.kubernetes.io/part-of: 'apps-with-ingress'
spec:
containers:
- name: appa
image: appa:1.0.0
imagePullPolicy: Always
env:
- name: DOTNET_LOGGING__CONSOLE__DISABLECOLORS
value: 'true'
- name: ASPNETCORE_URLS
value: 'http://*'
- name: PORT
value: '80'
- name: SERVICE__APPA__PROTOCOL
value: 'http'
- name: SERVICE__APPA__PORT
value: '80'
- name: SERVICE__APPA__HOST
value: 'appa'
- name: SERVICE__APPB__PROTOCOL
value: 'http'
- name: SERVICE__APPB__PORT
value: '80'
- name: SERVICE__APPB__HOST
value: 'appb'
ports:
- containerPort: 80
...
---
kind: Service
apiVersion: v1
metadata:
name: appa
labels:
app.kubernetes.io/name: 'appa'
app.kubernetes.io/part-of: 'apps-with-ingress'
spec:
selector:
app.kubernetes.io/name: appa
type: ClusterIP
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
...
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: appb
labels:
app.kubernetes.io/name: 'appb'
app.kubernetes.io/part-of: 'apps-with-ingress'
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: appb
template:
metadata:
labels:
app.kubernetes.io/name: 'appb'
app.kubernetes.io/part-of: 'apps-with-ingress'
spec:
containers:
- name: appb
image: appb:1.0.0
imagePullPolicy: Always
env:
- name: DOTNET_LOGGING__CONSOLE__DISABLECOLORS
value: 'true'
- name: ASPNETCORE_URLS
value: 'http://*'
- name: PORT
value: '80'
- name: SERVICE__APPB__PROTOCOL
value: 'http'
- name: SERVICE__APPB__PORT
value: '80'
- name: SERVICE__APPB__HOST
value: 'appb'
- name: SERVICE__APPA__PROTOCOL
value: 'http'
- name: SERVICE__APPA__PORT
value: '80'
- name: SERVICE__APPA__HOST
value: 'appa'
ports:
- containerPort: 80
...
---
kind: Service
apiVersion: v1
metadata:
name: appb
labels:
app.kubernetes.io/name: 'appb'
app.kubernetes.io/part-of: 'apps-with-ingress'
spec:
selector:
app.kubernetes.io/name: appb
type: ClusterIP
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
...
---
kind: Ingress
apiVersion: extensions/v1beta1
metadata:
name: ingress
annotations:
kubernetes.io/ingress.class: 'nginx'
nginx.ingress.kubernetes.io/rewrite-target: '/$2'
labels:
app.kubernetes.io/part-of: 'apps-with-ingress'
spec:
rules:
- http:
paths:
- backend:
serviceName: appa
servicePort: 80
path: /A(/|$)(.*)
- backend:
serviceName: appb
servicePort: 80
path: /B(/|$)(.*)
- host: a.example.com
http:
paths:
- backend:
serviceName: appa
servicePort: 80
path: /()(.*)
- host: b.example.com
http:
paths:
- backend:
serviceName: appb
servicePort: 80
path: /()(.*)
...

0
test/E2ETest/testassets/generate/apps-with-ingress.yaml → test/E2ETest/testassets/generate/apps-with-ingress.1.19.yaml

Loading…
Cancel
Save