-
-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(web operator): add localtunnel feature for easy webhook developm…
…ent.
- Loading branch information
Christoph Bühler
committed
Oct 13, 2023
1 parent
5c28a14
commit 0b5ca0a
Showing
7 changed files
with
189 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
47 changes: 47 additions & 0 deletions
47
src/KubeOps.Operator.Web/Builder/OperatorBuilderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using KubeOps.Abstractions.Builder; | ||
using KubeOps.Operator.Web.LocalTunnel; | ||
|
||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace KubeOps.Operator.Web.Builder; | ||
|
||
/// <summary> | ||
/// Method extensions for the operator builder to register web specific services. | ||
/// </summary> | ||
public static class OperatorBuilderExtensions | ||
{ | ||
/// <summary> | ||
/// Adds a hosted service to the system that creates a "Local Tunnel" | ||
/// (http://localtunnel.github.io/www/) to the running application. | ||
/// The tunnel points to the configured host/port configuration and then | ||
/// registers itself as webhook target within Kubernetes. This | ||
/// enables developers to easily create webhooks without the requirement | ||
/// of registering ngrok / localtunnel urls themselves. | ||
/// </summary> | ||
/// <param name="builder">The operator builder.</param> | ||
/// <param name="port">The desired port that the asp.net application will run on.</param> | ||
/// <param name="hostname">The desired hostname.</param> | ||
/// <returns>The builder for chaining.</returns> | ||
/// <example> | ||
/// Attach the development tunnel to the operator if in debug mode. | ||
/// <code> | ||
/// var builder = WebApplication.CreateBuilder(args); | ||
/// builder.Services | ||
/// .AddKubernetesOperator() | ||
/// .RegisterComponents() | ||
/// #if DEBUG | ||
/// .AddDevelopmentTunnel(5000) | ||
/// #endif | ||
/// ; | ||
/// </code> | ||
/// </example> | ||
public static IOperatorBuilder AddDevelopmentTunnel( | ||
this IOperatorBuilder builder, | ||
ushort port, | ||
string hostname = "localhost") | ||
{ | ||
builder.Services.AddHostedService<DevelopmentTunnelService>(); | ||
builder.Services.AddSingleton(new TunnelConfig(hostname, port)); | ||
return builder; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
130 changes: 130 additions & 0 deletions
130
src/KubeOps.Operator.Web/LocalTunnel/DevelopmentTunnelService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
using System.Reflection; | ||
|
||
using k8s; | ||
using k8s.Models; | ||
|
||
using KubeOps.Operator.Client; | ||
using KubeOps.Operator.Web.Webhooks.Mutation; | ||
using KubeOps.Operator.Web.Webhooks.Validation; | ||
using KubeOps.Transpiler; | ||
|
||
using Localtunnel; | ||
using Localtunnel.Endpoints.Http; | ||
using Localtunnel.Handlers.Kestrel; | ||
using Localtunnel.Processors; | ||
using Localtunnel.Tunnels; | ||
|
||
using Microsoft.Extensions.Hosting; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace KubeOps.Operator.Web.LocalTunnel; | ||
|
||
internal class DevelopmentTunnelService : IHostedService | ||
{ | ||
private readonly TunnelConfig _config; | ||
private readonly LocaltunnelClient _tunnelClient; | ||
private Tunnel? _tunnel; | ||
|
||
public DevelopmentTunnelService(ILoggerFactory loggerFactory, TunnelConfig config) | ||
{ | ||
_config = config; | ||
_tunnelClient = new(loggerFactory); | ||
} | ||
|
||
public async Task StartAsync(CancellationToken cancellationToken) | ||
{ | ||
_tunnel = await _tunnelClient.OpenAsync( | ||
new KestrelTunnelConnectionHandler( | ||
new HttpRequestProcessingPipelineBuilder() | ||
.Append(new HttpHostHeaderRewritingRequestProcessor(_config.Hostname)).Build(), | ||
new HttpTunnelEndpointFactory(_config.Hostname, _config.Port)), | ||
cancellationToken: cancellationToken); | ||
await _tunnel.StartAsync(cancellationToken: cancellationToken); | ||
await RegisterValidators(_tunnel.Information.Url); | ||
await RegisterMutators(_tunnel.Information.Url); | ||
} | ||
|
||
public Task StopAsync(CancellationToken cancellationToken) | ||
{ | ||
_tunnel?.Dispose(); | ||
return Task.CompletedTask; | ||
} | ||
|
||
private static async Task RegisterValidators(Uri uri) | ||
{ | ||
var validationWebhooks = Assembly | ||
.GetEntryAssembly()! | ||
.DefinedTypes | ||
.Where(t => t.BaseType?.IsGenericType == true && | ||
t.BaseType?.GetGenericTypeDefinition() == typeof(ValidationWebhook<>)) | ||
.Select(t => (HookTypeName: t.BaseType!.GenericTypeArguments[0].Name.ToLowerInvariant(), | ||
Entities.ToEntityMetadata(t.BaseType!.GenericTypeArguments[0]).Metadata)) | ||
.Select(hook => new V1ValidatingWebhook | ||
{ | ||
Name = $"validate.{hook.Metadata.SingularName}.{hook.Metadata.Group}.{hook.Metadata.Version}", | ||
MatchPolicy = "Exact", | ||
AdmissionReviewVersions = new[] { "v1" }, | ||
SideEffects = "None", | ||
Rules = new[] | ||
{ | ||
new V1RuleWithOperations | ||
{ | ||
Operations = new[] { "*" }, | ||
Resources = new[] { hook.Metadata.PluralName }, | ||
ApiGroups = new[] { hook.Metadata.Group }, | ||
ApiVersions = new[] { hook.Metadata.Version }, | ||
}, | ||
}, | ||
ClientConfig = new Admissionregistrationv1WebhookClientConfig | ||
{ | ||
Url = $"{uri}validate/{hook.HookTypeName}", | ||
}, | ||
}); | ||
|
||
var validatorConfig = new V1ValidatingWebhookConfiguration( | ||
metadata: new V1ObjectMeta(name: "dev-validators"), | ||
webhooks: validationWebhooks.ToList()).Initialize(); | ||
|
||
using var validatorClient = KubernetesClientFactory.Create<V1ValidatingWebhookConfiguration>(); | ||
await validatorClient.SaveAsync(validatorConfig); | ||
} | ||
|
||
private static async Task RegisterMutators(Uri uri) | ||
{ | ||
var mutationWebhooks = Assembly | ||
.GetEntryAssembly()! | ||
.DefinedTypes | ||
.Where(t => t.BaseType?.IsGenericType == true && | ||
t.BaseType?.GetGenericTypeDefinition() == typeof(MutationWebhook<>)) | ||
.Select(t => (HookTypeName: t.BaseType!.GenericTypeArguments[0].Name.ToLowerInvariant(), | ||
Entities.ToEntityMetadata(t.BaseType!.GenericTypeArguments[0]).Metadata)) | ||
.Select(hook => new V1MutatingWebhook | ||
{ | ||
Name = $"validate.{hook.Metadata.SingularName}.{hook.Metadata.Group}.{hook.Metadata.Version}", | ||
MatchPolicy = "Exact", | ||
AdmissionReviewVersions = new[] { "v1" }, | ||
SideEffects = "None", | ||
Rules = new[] | ||
{ | ||
new V1RuleWithOperations | ||
{ | ||
Operations = new[] { "*" }, | ||
Resources = new[] { hook.Metadata.PluralName }, | ||
ApiGroups = new[] { hook.Metadata.Group }, | ||
ApiVersions = new[] { hook.Metadata.Version }, | ||
}, | ||
}, | ||
ClientConfig = new Admissionregistrationv1WebhookClientConfig | ||
{ | ||
Url = $"{uri}validate/{hook.HookTypeName}", | ||
}, | ||
}); | ||
|
||
var mutatorConfig = new V1MutatingWebhookConfiguration( | ||
metadata: new V1ObjectMeta(name: "dev-mutators"), | ||
webhooks: mutationWebhooks.ToList()).Initialize(); | ||
|
||
using var mutatorClient = KubernetesClientFactory.Create<V1MutatingWebhookConfiguration>(); | ||
await mutatorClient.SaveAsync(mutatorConfig); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
namespace KubeOps.Operator.Web.LocalTunnel; | ||
|
||
internal record TunnelConfig(string Hostname, ushort Port); |