新增learn-kubernetes(https://github.com/yyong-brs/learn-kubernetes)相关文件
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.301-alpine AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY src/WebPingOperator.Model/WebPingOperator.Model.csproj ./WebPingOperator.Model/
|
||||
COPY src/WebPingOperator.WebPingerArchiveController/WebPingOperator.WebPingerArchiveController.csproj ./WebPingOperator.WebPingerArchiveController/
|
||||
|
||||
WORKDIR /src/WebPingOperator.WebPingerArchiveController
|
||||
RUN dotnet restore
|
||||
|
||||
COPY src /src
|
||||
RUN dotnet publish -c Release -o /out WebPingOperator.WebPingerArchiveController.csproj
|
||||
|
||||
# app image
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:3.1.5-alpine
|
||||
|
||||
WORKDIR /app
|
||||
ENTRYPOINT ["dotnet", "WebPingOperator.WebPingerArchiveController.dll"]
|
||||
|
||||
COPY --from=builder /out/ .
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.301-alpine AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY src/WebPingOperator.Model/WebPingOperator.Model.csproj ./WebPingOperator.Model/
|
||||
COPY src/WebPingOperator.Installer/WebPingOperator.Installer.csproj ./WebPingOperator.Installer/
|
||||
|
||||
WORKDIR /src/WebPingOperator.Installer
|
||||
RUN dotnet restore
|
||||
|
||||
COPY src /src
|
||||
RUN dotnet publish -c Release -o /out WebPingOperator.Installer.csproj
|
||||
|
||||
# app image
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:3.1.5-alpine
|
||||
|
||||
WORKDIR /app
|
||||
ENTRYPOINT ["dotnet", "WebPingOperator.Installer.dll"]
|
||||
|
||||
COPY --from=builder /out/ .
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.301-alpine AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY src/WebPingOperator.Model/WebPingOperator.Model.csproj ./WebPingOperator.Model/
|
||||
COPY src/WebPingOperator.WebPingerController/WebPingOperator.WebPingerController.csproj ./WebPingOperator.WebPingerController/
|
||||
|
||||
WORKDIR /src/WebPingOperator.WebPingerController
|
||||
RUN dotnet restore
|
||||
|
||||
COPY src /src
|
||||
RUN dotnet publish -c Release -o /out WebPingOperator.WebPingerController.csproj
|
||||
|
||||
# app image
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:3.1.5-alpine
|
||||
|
||||
WORKDIR /app
|
||||
ENTRYPOINT ["dotnet", "WebPingOperator.WebPingerController.dll"]
|
||||
|
||||
COPY --from=builder /out/ .
|
||||
@@ -0,0 +1,105 @@
|
||||
using k8s;
|
||||
using k8s.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using WebPingOperator.Model.CustomResources;
|
||||
|
||||
namespace WebPingOperator.Installer.Installers
|
||||
{
|
||||
public class WebPingerArchiveInstaller : IInstaller
|
||||
{
|
||||
private readonly Kubernetes _kubernetes;
|
||||
|
||||
public WebPingerArchiveInstaller(Kubernetes kubernetes)
|
||||
{
|
||||
_kubernetes = kubernetes;
|
||||
}
|
||||
|
||||
public async Task InstallAsync()
|
||||
{
|
||||
await EnsureWebPingerArchiveCrdAsync();
|
||||
}
|
||||
|
||||
private async Task EnsureWebPingerArchiveCrdAsync()
|
||||
{
|
||||
var crds = await _kubernetes.ListCustomResourceDefinitionAsync(
|
||||
fieldSelector: $"metadata.name={WebPingerArchive.Definition.Plural}.{WebPinger.Definition.Group}");
|
||||
|
||||
if (!crds.Items.Any())
|
||||
{
|
||||
var crd = new V1CustomResourceDefinition
|
||||
{
|
||||
Metadata = new V1ObjectMeta
|
||||
{
|
||||
Name = $"{WebPingerArchive.Definition.Plural}.{WebPingerArchive.Definition.Group}",
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ "kiamol", "ch20" },
|
||||
{ "operator", "web-ping" }
|
||||
}
|
||||
},
|
||||
Spec = new V1CustomResourceDefinitionSpec
|
||||
{
|
||||
Group = WebPingerArchive.Definition.Group,
|
||||
Scope = "Namespaced",
|
||||
Names = new V1CustomResourceDefinitionNames
|
||||
{
|
||||
Plural = WebPingerArchive.Definition.Plural,
|
||||
Singular = WebPingerArchive.Definition.Singular,
|
||||
Kind = WebPingerArchive.Definition.Kind,
|
||||
ShortNames = new List<string>
|
||||
{
|
||||
WebPingerArchive.Definition.ShortName
|
||||
}
|
||||
},
|
||||
Versions = new List<V1CustomResourceDefinitionVersion>
|
||||
{
|
||||
new V1CustomResourceDefinitionVersion
|
||||
{
|
||||
Name = "v1",
|
||||
Served = true,
|
||||
Storage = true,
|
||||
Schema = new V1CustomResourceValidation
|
||||
{
|
||||
OpenAPIV3Schema = new V1JSONSchemaProps
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, V1JSONSchemaProps>
|
||||
{
|
||||
{
|
||||
"spec",
|
||||
new V1JSONSchemaProps
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, V1JSONSchemaProps>
|
||||
{
|
||||
{
|
||||
"target",
|
||||
new V1JSONSchemaProps
|
||||
{
|
||||
Type = "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await _kubernetes.CreateCustomResourceDefinitionAsync(crd);
|
||||
Console.WriteLine($"** Created CRD for Kind: {WebPingerArchive.Definition.Kind}; ApiVersion: {WebPinger.Definition.Group}/{WebPinger.Definition.Version}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"** CRD already exists for Kind: {WebPingerArchive.Definition.Kind}; ApiVersion: {WebPinger.Definition.Group}/{WebPinger.Definition.Version}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using k8s;
|
||||
using k8s.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using WebPingOperator.Model.CustomResources;
|
||||
|
||||
namespace WebPingOperator.Installer.Installers
|
||||
{
|
||||
public class WebPingerInstaller : IInstaller
|
||||
{
|
||||
private readonly Kubernetes _kubernetes;
|
||||
|
||||
public WebPingerInstaller(Kubernetes kubernetes)
|
||||
{
|
||||
_kubernetes = kubernetes;
|
||||
}
|
||||
|
||||
public async Task InstallAsync()
|
||||
{
|
||||
await EnsureWebPingerCrdAsync();
|
||||
}
|
||||
|
||||
private async Task EnsureWebPingerCrdAsync()
|
||||
{
|
||||
var crds = await _kubernetes.ListCustomResourceDefinitionAsync(
|
||||
fieldSelector: $"metadata.name={WebPinger.Definition.Plural}.{WebPinger.Definition.Group}");
|
||||
|
||||
if (!crds.Items.Any())
|
||||
{
|
||||
var crd = new V1CustomResourceDefinition
|
||||
{
|
||||
Metadata = new V1ObjectMeta
|
||||
{
|
||||
Name = $"{WebPinger.Definition.Plural}.{WebPinger.Definition.Group}",
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ "kiamol", "ch20" },
|
||||
{ "operator", "web-ping" }
|
||||
}
|
||||
},
|
||||
Spec = new V1CustomResourceDefinitionSpec
|
||||
{
|
||||
Group = WebPinger.Definition.Group,
|
||||
Scope = "Namespaced",
|
||||
Names = new V1CustomResourceDefinitionNames
|
||||
{
|
||||
Plural = WebPinger.Definition.Plural,
|
||||
Singular = WebPinger.Definition.Singular,
|
||||
Kind = WebPinger.Definition.Kind,
|
||||
ShortNames = new List<string>
|
||||
{
|
||||
WebPinger.Definition.ShortName
|
||||
}
|
||||
},
|
||||
Versions = new List<V1CustomResourceDefinitionVersion>
|
||||
{
|
||||
new V1CustomResourceDefinitionVersion
|
||||
{
|
||||
Name = "v1",
|
||||
Served = true,
|
||||
Storage = true,
|
||||
Schema = new V1CustomResourceValidation
|
||||
{
|
||||
OpenAPIV3Schema = new V1JSONSchemaProps
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, V1JSONSchemaProps>
|
||||
{
|
||||
{
|
||||
"spec",
|
||||
new V1JSONSchemaProps
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, V1JSONSchemaProps>
|
||||
{
|
||||
{
|
||||
"target",
|
||||
new V1JSONSchemaProps
|
||||
{
|
||||
Type = "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"interval",
|
||||
new V1JSONSchemaProps
|
||||
{
|
||||
Type = "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method",
|
||||
new V1JSONSchemaProps
|
||||
{
|
||||
Type = "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await _kubernetes.CreateCustomResourceDefinitionAsync(crd);
|
||||
Console.WriteLine($"** Created CRD for Kind: {WebPinger.Definition.Kind}; ApiVersion: {WebPinger.Definition.Group}/{WebPinger.Definition.Version}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"** CRD already exists for Kind: {WebPinger.Definition.Kind}; ApiVersion: {WebPinger.Definition.Group}/{WebPinger.Definition.Version}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebPingOperator.Installer.Installers
|
||||
{
|
||||
public interface IInstaller
|
||||
{
|
||||
Task InstallAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using k8s;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using WebPingOperator.Installer.Installers;
|
||||
|
||||
namespace WebPingOperator.Installer
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddJsonFile("config/appsettings.json", optional: true)
|
||||
.Build();
|
||||
|
||||
var kubeConfig = KubernetesClientConfiguration.IsInCluster() ?
|
||||
KubernetesClientConfiguration.InClusterConfig() :
|
||||
new KubernetesClientConfiguration { Host = "http://localhost:8001" };
|
||||
|
||||
var kubernetes = new Kubernetes(kubeConfig);
|
||||
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddSingleton(config)
|
||||
.AddSingleton(kubernetes)
|
||||
.AddTransient<IInstaller, WebPingerInstaller>()
|
||||
.AddTransient<IInstaller, WebPingerArchiveInstaller>()
|
||||
.BuildServiceProvider();
|
||||
|
||||
var installers = serviceProvider.GetServices<IInstaller>();
|
||||
foreach (var installer in installers)
|
||||
{
|
||||
await installer.InstallAsync();
|
||||
}
|
||||
|
||||
Console.WriteLine("* Done.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="KubernetesClient" Version="2.0.26" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WebPingOperator.Model\WebPingOperator.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,50 @@
|
||||
using k8s;
|
||||
using k8s.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WebPingOperator.Model.CustomResources
|
||||
{
|
||||
public class WebPinger : KubernetesObject
|
||||
{
|
||||
public V1ObjectMeta Metadata { get; set; }
|
||||
|
||||
public WebPingSpec Spec { get; set; }
|
||||
|
||||
public class WebPingSpec
|
||||
{
|
||||
public string Target { get; set; }
|
||||
|
||||
public string Interval { get; set; }
|
||||
|
||||
public string Method { get; set; }
|
||||
|
||||
public int GetIntervalMilliseconds()
|
||||
{
|
||||
var interval = int.Parse(Interval[0..^1]);
|
||||
var measure = Interval.ToLower()[^1];
|
||||
if (measure == 'm')
|
||||
{
|
||||
interval *= 60;
|
||||
}
|
||||
//otherwise assume seconds
|
||||
return interval * 1000;
|
||||
}
|
||||
|
||||
public string GetMethod()
|
||||
{
|
||||
return Method ?? "HEAD";
|
||||
}
|
||||
}
|
||||
|
||||
public struct Definition
|
||||
{
|
||||
public const string Group = "ch20.kiamol.net";
|
||||
public const string Version = "v1";
|
||||
public const string Plural = "webpingers";
|
||||
public const string Singular = "webpinger";
|
||||
public const string Kind = "WebPinger";
|
||||
public const string ShortName = "wp";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using k8s;
|
||||
using k8s.Models;
|
||||
using System;
|
||||
|
||||
namespace WebPingOperator.Model.CustomResources
|
||||
{
|
||||
public class WebPingerArchive : KubernetesObject
|
||||
{
|
||||
public V1ObjectMeta Metadata { get; set; }
|
||||
|
||||
public WebPingSpec Spec { get; set; }
|
||||
|
||||
public class WebPingSpec
|
||||
{
|
||||
public string Target { get; set; }
|
||||
}
|
||||
|
||||
public struct Definition
|
||||
{
|
||||
public const string Group = "ch20.kiamol.net";
|
||||
public const string Version = "v1";
|
||||
public const string Plural = "webpingerarchives";
|
||||
public const string Singular = "webpingerarchive";
|
||||
public const string Kind = "WebPingerArchive";
|
||||
public const string ShortName = "wpa";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="KubernetesClient" Version="2.0.26" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,105 @@
|
||||
using k8s;
|
||||
using k8s.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using WebPingOperator.Model.CustomResources;
|
||||
|
||||
namespace WebPingOperator.WebPingerArchiveController.Handlers
|
||||
{
|
||||
public class WebPingerArchiveAddedHandler
|
||||
{
|
||||
private readonly Kubernetes _kubernetes;
|
||||
|
||||
public WebPingerArchiveAddedHandler(Kubernetes kubernetes)
|
||||
{
|
||||
_kubernetes = kubernetes;
|
||||
}
|
||||
|
||||
public async Task<bool> HandleAsync(WebPingerArchive archive)
|
||||
{
|
||||
await CreateJobAsync(archive);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> CreateJobAsync(WebPingerArchive archive)
|
||||
{
|
||||
// check a pinger exists to archive:
|
||||
var services = await _kubernetes.ListNamespacedServiceAsync(
|
||||
Program.NamespaceName,
|
||||
labelSelector: $"app=web-ping,target={archive.Spec.Target}");
|
||||
if (!services.Items.Any())
|
||||
{
|
||||
Console.WriteLine($"** No WebPinger Service exists for target: {archive.Spec.Target}, in namespace: {Program.NamespaceName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var pingerServiceName = services.Items.First().Metadata.Name;
|
||||
var name = $"wpa-{archive.Metadata.Name}-{archive.Metadata.CreationTimestamp:yyMMdd-HHmm}";
|
||||
var jobs = await _kubernetes.ListNamespacedJobAsync(
|
||||
Program.NamespaceName,
|
||||
fieldSelector: $"metadata.name={name}");
|
||||
|
||||
if (!jobs.Items.Any())
|
||||
{
|
||||
var job = new V1Job
|
||||
{
|
||||
Metadata = new V1ObjectMeta
|
||||
{
|
||||
Name = name,
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ "kiamol", "ch20" },
|
||||
}
|
||||
},
|
||||
Spec = new V1JobSpec
|
||||
{
|
||||
Completions = 1,
|
||||
Template = new V1PodTemplateSpec
|
||||
{
|
||||
Metadata = new V1ObjectMeta
|
||||
{
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ "app", "web-ping-archive"},
|
||||
{ "target", archive.Spec.Target }
|
||||
}
|
||||
},
|
||||
Spec = new V1PodSpec
|
||||
{
|
||||
AutomountServiceAccountToken = false,
|
||||
RestartPolicy = "Never",
|
||||
Containers = new List<V1Container>
|
||||
{
|
||||
new V1Container
|
||||
{
|
||||
Name = "archiver",
|
||||
Image = "kiamol/ch20-web-ping-archiver",
|
||||
Env = new List<V1EnvVar>
|
||||
{
|
||||
new V1EnvVar
|
||||
{
|
||||
Name = "WEB_PING_URL",
|
||||
Value = $"http://{pingerServiceName}:8080/archive"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await _kubernetes.CreateNamespacedJobAsync(job, Program.NamespaceName);
|
||||
Console.WriteLine($"** Created Job: {name}, in namespace: {Program.NamespaceName}");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"** Job exists: {name}, in namespace: {Program.NamespaceName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using k8s;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using WebPingOperator.Model.CustomResources;
|
||||
|
||||
namespace WebPingOperator.WebPingerArchiveController.Handlers
|
||||
{
|
||||
public class WebPingerArchiveDeletedHandler
|
||||
{
|
||||
private readonly Kubernetes _kubernetes;
|
||||
|
||||
public WebPingerArchiveDeletedHandler(Kubernetes kubernetes)
|
||||
{
|
||||
_kubernetes = kubernetes;
|
||||
}
|
||||
|
||||
public async Task<bool> HandleAsync(WebPingerArchive archive)
|
||||
{
|
||||
await DeleteJobAsync(archive);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> DeleteJobAsync(WebPingerArchive archive)
|
||||
{
|
||||
var name = $"wpa-{archive.Metadata.Name}-{archive.Metadata.CreationTimestamp:yyMMdd-HHmm}";
|
||||
var jobs = await _kubernetes.ListNamespacedJobAsync(
|
||||
Program.NamespaceName,
|
||||
fieldSelector: $"metadata.name={name}");
|
||||
|
||||
if (jobs.Items.Any())
|
||||
{
|
||||
await _kubernetes.DeleteNamespacedJobAsync(name, Program.NamespaceName);
|
||||
Console.WriteLine($"** Deleted Job: {name}, in namespace: {Program.NamespaceName}");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"** No Job to delete: {name}, in namespace: {Program.NamespaceName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using k8s;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using WebPingOperator.Model.CustomResources;
|
||||
using WebPingOperator.WebPingerArchiveController.Handlers;
|
||||
|
||||
namespace WebPingOperator.WebPingerArchiveController
|
||||
{
|
||||
class Program
|
||||
{
|
||||
private static ServiceProvider _ServiceProvider;
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddJsonFile("config/appsettings.json", optional: true)
|
||||
.Build();
|
||||
|
||||
var kubeConfig = KubernetesClientConfiguration.IsInCluster() ?
|
||||
KubernetesClientConfiguration.InClusterConfig() :
|
||||
new KubernetesClientConfiguration { Host = "http://localhost:8001" };
|
||||
|
||||
var kubernetes = new Kubernetes(kubeConfig);
|
||||
|
||||
_ServiceProvider = new ServiceCollection()
|
||||
.AddSingleton(config)
|
||||
.AddSingleton(kubernetes)
|
||||
.AddTransient<WebPingerArchiveAddedHandler>()
|
||||
.AddTransient<WebPingerArchiveDeletedHandler>()
|
||||
.BuildServiceProvider();
|
||||
|
||||
// TODO - this hangs and then fails if there are no objects
|
||||
var result = await kubernetes.ListNamespacedCustomObjectWithHttpMessagesAsync(
|
||||
group: WebPingerArchive.Definition.Group,
|
||||
version: WebPingerArchive.Definition.Version,
|
||||
plural: WebPingerArchive.Definition.Plural,
|
||||
namespaceParameter: "default",
|
||||
watch: true);
|
||||
|
||||
using (result.Watch<WebPingerArchive, object>(async (type, item) => await Handle(type, item)))
|
||||
{
|
||||
Console.WriteLine("* Watching for WebPingerArchive events");
|
||||
|
||||
var ctrlc = new ManualResetEventSlim(false);
|
||||
Console.CancelKeyPress += (sender, eventArgs) => ctrlc.Set();
|
||||
ctrlc.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Handle(WatchEventType type, WebPingerArchive archive)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case WatchEventType.Added:
|
||||
var addedhandler = _ServiceProvider.GetService<WebPingerArchiveAddedHandler>();
|
||||
await addedhandler.HandleAsync(archive);
|
||||
Console.WriteLine($"* Handled event: {type}, for: {archive.Metadata.Name}");
|
||||
break;
|
||||
|
||||
case WatchEventType.Deleted:
|
||||
var deletedHandler = _ServiceProvider.GetService<WebPingerArchiveDeletedHandler>();
|
||||
await deletedHandler.HandleAsync(archive);
|
||||
Console.WriteLine($"* Handled event: {type}, for: {archive.Metadata.Name}");
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"* Ignored event: {type}, for: {archive.Metadata.Name}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - move to config:
|
||||
public const string NamespaceName = "default";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="KubernetesClient" Version="2.0.26" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WebPingOperator.Model\WebPingOperator.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,87 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace WebPingOperator.WebPingerController {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class ConfigMapData {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal ConfigMapData() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WebPingOperator.WebPingerController.ConfigMapData", typeof(ConfigMapData).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to const { format, transports } = require('winston');
|
||||
/// var logConfig = module.exports = {};
|
||||
///
|
||||
/// logConfig.options = {
|
||||
/// transports: [
|
||||
/// new transports.Console({
|
||||
/// level: 'debug',
|
||||
/// format: format.combine(
|
||||
/// format.splat(),
|
||||
/// format.printf(log => {
|
||||
/// return `${log.message}`
|
||||
/// })
|
||||
/// )
|
||||
/// }),
|
||||
/// new transports.File({
|
||||
/// level: 'debu [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
internal static string logConfig {
|
||||
get {
|
||||
return ResourceManager.GetString("logConfig", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="logConfig" xml:space="preserve">
|
||||
<value>const { format, transports } = require('winston');
|
||||
var logConfig = module.exports = {};
|
||||
|
||||
logConfig.options = {
|
||||
transports: [
|
||||
new transports.Console({
|
||||
level: 'debug',
|
||||
format: format.combine(
|
||||
format.splat(),
|
||||
format.printf(log => {
|
||||
return `${log.message}`
|
||||
})
|
||||
)
|
||||
}),
|
||||
new transports.File({
|
||||
level: 'debug',
|
||||
format: format.combine(
|
||||
format.splat(),
|
||||
format.timestamp(),
|
||||
format.json()
|
||||
),
|
||||
filename: '/logs/web-ping.log',
|
||||
maxsize: 104857600, // 100MB
|
||||
maxFiles: 1,
|
||||
maxRetries: 10
|
||||
})
|
||||
]
|
||||
};</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,253 @@
|
||||
using k8s;
|
||||
using k8s.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using WebPingOperator.Model.CustomResources;
|
||||
|
||||
namespace WebPingOperator.WebPingerController.Handlers
|
||||
{
|
||||
public class WebPingerAddedHandler
|
||||
{
|
||||
private readonly Kubernetes _kubernetes;
|
||||
|
||||
public WebPingerAddedHandler(Kubernetes kubernetes)
|
||||
{
|
||||
_kubernetes = kubernetes;
|
||||
}
|
||||
|
||||
public async Task<bool> HandleAsync(WebPinger pinger)
|
||||
{
|
||||
await EnsureConfigMapAsync(pinger);
|
||||
await EnsureDeploymentAsync(pinger);
|
||||
await EnsureServiceAsync(pinger);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> EnsureConfigMapAsync(WebPinger pinger)
|
||||
{
|
||||
var name = $"wp-{pinger.Metadata.Name}-config";
|
||||
var configMaps = await _kubernetes.ListNamespacedConfigMapAsync(
|
||||
Program.NamespaceName,
|
||||
fieldSelector: $"metadata.name={name}");
|
||||
|
||||
if (!configMaps.Items.Any())
|
||||
{
|
||||
var configMap = new V1ConfigMap
|
||||
{
|
||||
Metadata = new V1ObjectMeta
|
||||
{
|
||||
Name = name,
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ "kiamol", "ch20" },
|
||||
}
|
||||
},
|
||||
Data = new Dictionary<string, string>
|
||||
{
|
||||
{ "logConfig.js", ConfigMapData.logConfig.Replace("\r\n", "\n") }
|
||||
}
|
||||
};
|
||||
|
||||
await _kubernetes.CreateNamespacedConfigMapAsync(configMap, Program.NamespaceName);
|
||||
Console.WriteLine($"** Created ConfigMap: {name}, in namespace: {Program.NamespaceName}");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"** ConfigMap exists: {name}, in namespace: {Program.NamespaceName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> EnsureDeploymentAsync(WebPinger pinger)
|
||||
{
|
||||
var name = $"wp-{pinger.Metadata.Name}";
|
||||
var deployments = await _kubernetes.ListNamespacedDeploymentAsync(
|
||||
Program.NamespaceName,
|
||||
fieldSelector: $"metadata.name={name}");
|
||||
|
||||
if (!deployments.Items.Any())
|
||||
{
|
||||
var deployment = new V1Deployment
|
||||
{
|
||||
Metadata = new V1ObjectMeta
|
||||
{
|
||||
Name = name,
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ "kiamol", "ch20" },
|
||||
}
|
||||
},
|
||||
Spec = new V1DeploymentSpec
|
||||
{
|
||||
Selector = new V1LabelSelector
|
||||
{
|
||||
MatchLabels = new Dictionary<string, string>()
|
||||
{
|
||||
{ "app", "web-ping"},
|
||||
{ "instance", name }
|
||||
}
|
||||
},
|
||||
Template = new V1PodTemplateSpec
|
||||
{
|
||||
Metadata = new V1ObjectMeta
|
||||
{
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ "app", "web-ping"},
|
||||
{ "instance", name },
|
||||
{ "target", pinger.Spec.Target }
|
||||
}
|
||||
},
|
||||
Spec = new V1PodSpec
|
||||
{
|
||||
AutomountServiceAccountToken = false,
|
||||
Containers = new List<V1Container>
|
||||
{
|
||||
new V1Container
|
||||
{
|
||||
Name = "web",
|
||||
Image = "kiamol/ch10-web-ping",
|
||||
Env = new List<V1EnvVar>
|
||||
{
|
||||
new V1EnvVar
|
||||
{
|
||||
Name = "INTERVAL",
|
||||
Value = pinger.Spec.GetIntervalMilliseconds().ToString()
|
||||
},
|
||||
new V1EnvVar
|
||||
{
|
||||
Name = "TARGET",
|
||||
Value = pinger.Spec.Target
|
||||
},
|
||||
new V1EnvVar
|
||||
{
|
||||
Name = "METHOD",
|
||||
Value = pinger.Spec.GetMethod()
|
||||
}
|
||||
},
|
||||
VolumeMounts = new List<V1VolumeMount>
|
||||
{
|
||||
new V1VolumeMount
|
||||
{
|
||||
Name= "config",
|
||||
MountPath = "/app/config",
|
||||
ReadOnlyProperty = true
|
||||
},
|
||||
new V1VolumeMount
|
||||
{
|
||||
Name= "logs",
|
||||
MountPath = "/logs"
|
||||
}
|
||||
}
|
||||
},
|
||||
new V1Container
|
||||
{
|
||||
Name = "api",
|
||||
Image = "kiamol/ch20-log-archiver",
|
||||
Ports = new List<V1ContainerPort>
|
||||
{
|
||||
new V1ContainerPort
|
||||
{
|
||||
Name = "api",
|
||||
ContainerPort = 80
|
||||
}
|
||||
},
|
||||
VolumeMounts = new List<V1VolumeMount>
|
||||
{
|
||||
new V1VolumeMount
|
||||
{
|
||||
Name= "logs",
|
||||
MountPath = "/logs"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
Volumes = new List<V1Volume>
|
||||
{
|
||||
new V1Volume
|
||||
{
|
||||
Name = "config",
|
||||
ConfigMap = new V1ConfigMapVolumeSource
|
||||
{
|
||||
Name = $"{name}-config"
|
||||
}
|
||||
},
|
||||
new V1Volume
|
||||
{
|
||||
Name = "logs",
|
||||
EmptyDir = new V1EmptyDirVolumeSource()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await _kubernetes.CreateNamespacedDeploymentAsync(deployment, Program.NamespaceName);
|
||||
Console.WriteLine($"** Created Deployment: {name}, in namespace: {Program.NamespaceName}");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"** Deployment exists: {name}, in namespace: {Program.NamespaceName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> EnsureServiceAsync(WebPinger pinger)
|
||||
{
|
||||
var name = $"wp-{pinger.Metadata.Name}";
|
||||
var services = await _kubernetes.ListNamespacedServiceAsync(
|
||||
Program.NamespaceName,
|
||||
fieldSelector: $"metadata.name={name}");
|
||||
|
||||
if (!services.Items.Any())
|
||||
{
|
||||
var service = new V1Service
|
||||
{
|
||||
Metadata = new V1ObjectMeta
|
||||
{
|
||||
Name = name,
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ "kiamol", "ch20"},
|
||||
{ "app", "web-ping"},
|
||||
{ "target", pinger.Spec.Target }
|
||||
}
|
||||
},
|
||||
Spec = new V1ServiceSpec
|
||||
{
|
||||
Type = "ClusterIP",
|
||||
Ports = new List<V1ServicePort>()
|
||||
{
|
||||
new V1ServicePort
|
||||
{
|
||||
Port = 8080,
|
||||
TargetPort = "api"
|
||||
}
|
||||
},
|
||||
Selector = new Dictionary<string, string>()
|
||||
{
|
||||
{ "app", "web-ping"},
|
||||
{ "instance", name }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await _kubernetes.CreateNamespacedServiceAsync(service, Program.NamespaceName);
|
||||
Console.WriteLine($"** Created Service: {name}, in namespace: {Program.NamespaceName}");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"** Service exists: {name}, in namespace: {Program.NamespaceName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using k8s;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using WebPingOperator.Model.CustomResources;
|
||||
|
||||
namespace WebPingOperator.WebPingerController.Handlers
|
||||
{
|
||||
public class WebPingerDeletedHandler
|
||||
{
|
||||
private readonly Kubernetes _kubernetes;
|
||||
|
||||
public WebPingerDeletedHandler(Kubernetes kubernetes)
|
||||
{
|
||||
_kubernetes = kubernetes;
|
||||
}
|
||||
|
||||
public async Task<bool> HandleAsync(WebPinger pinger)
|
||||
{
|
||||
await DeleteServiceAsync(pinger);
|
||||
await DeleteDeploymentAsync(pinger);
|
||||
await DeleteConfigMapAsync(pinger);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> DeleteServiceAsync(WebPinger pinger)
|
||||
{
|
||||
var name = $"wp-{pinger.Metadata.Name}";
|
||||
var services = await _kubernetes.ListNamespacedServiceAsync(
|
||||
Program.NamespaceName,
|
||||
fieldSelector: $"metadata.name={name}");
|
||||
|
||||
if (services.Items.Any())
|
||||
{
|
||||
await _kubernetes.DeleteNamespacedServiceAsync(name, Program.NamespaceName);
|
||||
Console.WriteLine($"** Deleted Service: {name}, in namespace: {Program.NamespaceName}");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"** No Service to delete: {name}, in namespace: {Program.NamespaceName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> DeleteDeploymentAsync(WebPinger pinger)
|
||||
{
|
||||
var name = $"wp-{pinger.Metadata.Name}";
|
||||
var deployments = await _kubernetes.ListNamespacedDeploymentAsync(
|
||||
Program.NamespaceName,
|
||||
fieldSelector: $"metadata.name={name}");
|
||||
|
||||
if (deployments.Items.Any())
|
||||
{
|
||||
await _kubernetes.DeleteNamespacedDeploymentAsync(name, Program.NamespaceName);
|
||||
Console.WriteLine($"** Deleted Deployment: {name}, in namespace: {Program.NamespaceName}");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"** No Deployment to delete: {name}, in namespace: {Program.NamespaceName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> DeleteConfigMapAsync(WebPinger pinger)
|
||||
{
|
||||
var name = $"wp-{pinger.Metadata.Name}-config";
|
||||
var configMaps = await _kubernetes.ListNamespacedConfigMapAsync(
|
||||
Program.NamespaceName,
|
||||
fieldSelector: $"metadata.name={name}");
|
||||
|
||||
if (configMaps.Items.Any())
|
||||
{
|
||||
await _kubernetes.DeleteNamespacedConfigMapAsync(name, Program.NamespaceName);
|
||||
Console.WriteLine($"** Deleted ConfigMap: {name}, in namespace: {Program.NamespaceName}");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"** No ConfigMap to delete: {name}, in namespace: {Program.NamespaceName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using k8s;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using WebPingOperator.Model.CustomResources;
|
||||
using WebPingOperator.WebPingerController.Handlers;
|
||||
|
||||
namespace WebPingOperator.WebPingerController
|
||||
{
|
||||
class Program
|
||||
{
|
||||
private static ServiceProvider _ServiceProvider;
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddJsonFile("config/appsettings.json", optional: true)
|
||||
.Build();
|
||||
|
||||
var kubeConfig = KubernetesClientConfiguration.IsInCluster() ?
|
||||
KubernetesClientConfiguration.InClusterConfig() :
|
||||
new KubernetesClientConfiguration { Host = "http://localhost:8001" };
|
||||
|
||||
var kubernetes = new Kubernetes(kubeConfig);
|
||||
|
||||
_ServiceProvider = new ServiceCollection()
|
||||
.AddSingleton(config)
|
||||
.AddSingleton(kubernetes)
|
||||
.AddTransient<WebPingerAddedHandler>()
|
||||
.AddTransient<WebPingerDeletedHandler>()
|
||||
.BuildServiceProvider();
|
||||
|
||||
// TODO - this hangs and then fails if there are no objects
|
||||
var result = await kubernetes.ListNamespacedCustomObjectWithHttpMessagesAsync(
|
||||
group: WebPinger.Definition.Group,
|
||||
version: WebPinger.Definition.Version,
|
||||
plural: WebPinger.Definition.Plural,
|
||||
namespaceParameter: "default",
|
||||
watch: true);
|
||||
|
||||
using (result.Watch<WebPinger, object>(async (type, item) => await Handle(type, item)))
|
||||
{
|
||||
Console.WriteLine("* Watching for WebPinger events");
|
||||
|
||||
var ctrlc = new ManualResetEventSlim(false);
|
||||
Console.CancelKeyPress += (sender, eventArgs) => ctrlc.Set();
|
||||
ctrlc.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Handle(WatchEventType type, WebPinger pinger)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case WatchEventType.Added:
|
||||
var addedhandler = _ServiceProvider.GetService<WebPingerAddedHandler>();
|
||||
await addedhandler.HandleAsync(pinger);
|
||||
Console.WriteLine($"* Handled event: {type}, for: {pinger.Metadata.Name}");
|
||||
break;
|
||||
|
||||
case WatchEventType.Deleted:
|
||||
var deletedHandler = _ServiceProvider.GetService<WebPingerDeletedHandler>();
|
||||
await deletedHandler.HandleAsync(pinger);
|
||||
Console.WriteLine($"* Handled event: {type}, for: {pinger.Metadata.Name}");
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"* Ignored event: {type}, for: {pinger.Metadata.Name}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - move to config:
|
||||
public const string NamespaceName = "default";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="KubernetesClient" Version="2.0.26" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WebPingOperator.Model\WebPingOperator.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="ConfigMapData.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>ConfigMapData.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="ConfigMapData.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>ConfigMapData.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30309.148
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebPingOperator.WebPingerController", ".\WebPingOperator.WebPingerController\WebPingOperator.WebPingerController.csproj", "{27D653BB-04EA-470B-BE5C-A25D78D9DF8A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebPingOperator.Installer", ".\WebPingOperator.Installer\WebPingOperator.Installer.csproj", "{DFCDEACF-E27B-412F-8636-5DA7A3D38FBC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebPingOperator.Model", ".\WebPingOperator.Model\WebPingOperator.Model.csproj", "{085364F0-8DB7-456B-AA2C-1594EB6329EF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebPingOperator.WebPingerArchiveController", ".\WebPingOperator.WebPingerArchiveController\WebPingOperator.WebPingerArchiveController.csproj", "{AAEE6551-29F4-427B-BED1-198DE14653D2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{27D653BB-04EA-470B-BE5C-A25D78D9DF8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{27D653BB-04EA-470B-BE5C-A25D78D9DF8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{27D653BB-04EA-470B-BE5C-A25D78D9DF8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{27D653BB-04EA-470B-BE5C-A25D78D9DF8A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DFCDEACF-E27B-412F-8636-5DA7A3D38FBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DFCDEACF-E27B-412F-8636-5DA7A3D38FBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DFCDEACF-E27B-412F-8636-5DA7A3D38FBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DFCDEACF-E27B-412F-8636-5DA7A3D38FBC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{085364F0-8DB7-456B-AA2C-1594EB6329EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{085364F0-8DB7-456B-AA2C-1594EB6329EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{085364F0-8DB7-456B-AA2C-1594EB6329EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{085364F0-8DB7-456B-AA2C-1594EB6329EF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AAEE6551-29F4-427B-BED1-198DE14653D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AAEE6551-29F4-427B-BED1-198DE14653D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AAEE6551-29F4-427B-BED1-198DE14653D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AAEE6551-29F4-427B-BED1-198DE14653D2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6F0A108E-19CF-43E6-911D-5E11AAD9A0E3}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Reference in New Issue
Block a user