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..."); 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."); 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 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)); ingress.Outputs.Add(await KubernetesManifestGenerator.CreateIngress(output, application, ingress));
return Task.CompletedTask;
} }
} }
} }

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

@ -5,6 +5,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using YamlDotNet.Core; using YamlDotNet.Core;
using YamlDotNet.RepresentationModel; using YamlDotNet.RepresentationModel;
@ -12,15 +13,25 @@ namespace Microsoft.Tye
{ {
internal static class KubernetesManifestGenerator 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, OutputContext output,
ApplicationBuilder application, ApplicationBuilder application,
IngressBuilder ingress) IngressBuilder ingress)
{ {
var root = new YamlMappingNode(); var root = new YamlMappingNode();
var k8sVersion = await KubectlDetector.GetKubernetesServerVersion(output);
root.Add("kind", "Ingress"); 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(); var metadata = new YamlMappingNode();
root.Add("metadata", metadata); root.Add("metadata", metadata);
@ -74,10 +85,6 @@ namespace Microsoft.Tye
var backend = new YamlMappingNode(); var backend = new YamlMappingNode();
path.Add("backend", backend); 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); var service = application.Services.FirstOrDefault(s => s.Name == ingressRule.Service);
if (service is null) if (service is null)
@ -91,11 +98,25 @@ namespace Microsoft.Tye
throw new InvalidOperationException($"Could not resolve an http binding for service '{service.Name}'."); 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: // Tye implements path matching similar to this example:
// https://kubernetes.github.io/ingress-nginx/examples/rewrite/ // https://kubernetes.github.io/ingress-nginx/examples/rewrite/
@ -120,7 +141,6 @@ namespace Microsoft.Tye
} }
// Only support prefix matching for now // 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; return;
} }
if (!await KubectlDetector.IsKubectlInstalledAsync(output)) if (await KubectlDetector.GetKubernetesServerVersion(output) == null)
{ {
throw new CommandException($"Cannot validate ingress because kubectl is not installed."); 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. // 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 // The first time we apply the ingress controller, the validating webhook will not have started.
// saying "ingress-nginx-admission-create" is invalid. Therefore, we are going to blindly assume the // This causes an error to be returned from the process. As this always happens, we are going to
// controller successfully ran. // 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.WriteDebugLine($"Running 'kubectl apply'");
output.WriteCommandLine("kubectl", $"apply -f \"https://aka.ms/tye/ingress/deploy\""); 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. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.Tye namespace Microsoft.Tye
{ {
internal static class KubectlDetector 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); 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) if (!_kubectlInstalled.IsValueCreated)
{ {
@ -30,19 +31,34 @@ namespace Microsoft.Tye
return _kubectlConnectedToCluster.Value; return _kubectlConnectedToCluster.Value;
} }
private static async Task<bool> DetectKubectlInstalled() private static async Task<Version?> GetKubectlVersion()
{ {
try try
{ {
// Ignoring the exit code and relying on Process to throw if kubectl is not found // 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. // kubectl version will return non-zero if you're not connected to a cluster.
await ProcessUtil.RunAsync("kubectl", "version", throwOnError: false); var result = await ProcessUtil.RunAsync("kubectl", "version -o json", throwOnError: false);
return true;
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) catch (Exception)
{ {
// Unfortunately, process throws // 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(); 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."); 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="..\Test.Infrastructure\Test.Infrastructure.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Tye.Core\Microsoft.Tye.Core.csproj" /> <ProjectReference Include="..\..\src\Microsoft.Tye.Core\Microsoft.Tye.Core.csproj" />
<ProjectReference Include="..\..\src\tye\tye.csproj" /> <ProjectReference Include="..\..\src\tye\tye.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="testassets\**\*" CopyToOutputDirectory="PreserveNewest" /> <Content Include="testassets\**\*" CopyToOutputDirectory="PreserveNewest" />
<Compile Remove="testassets\**\*" /> <Compile Remove="testassets\**\*" />
<None Remove="testassets\generate\apps-with-ingress.1.18.yaml" />
<Compile Include="..\..\src\shared\KubectlDetector.cs" Link="KubectlDetector.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

12
test/E2ETest/TyeGenerateTests.cs

@ -379,9 +379,17 @@ namespace E2ETest
// name of application is the folder // name of application is the folder
var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{applicationName}-generate-{environment}.yaml")); 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, "appa");
await DockerAssert.AssertImageExistsAsync(output, "appb"); 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