Skip to content

Commit 1ab7520

Browse files
authored
Merge pull request #24 from ArchetypicalSoftware/KindDataAutomation
Automatically generate KindVersionInfo daily or if Kind Version for c…
2 parents eabc09b + 697c801 commit 1ab7520

33 files changed

+1647
-205
lines changed

cli/src/Vdk/Commands/AppCommand.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ namespace Vdk.Commands;
55

66
public class AppCommand: RootCommand
77
{
8-
public AppCommand(CreateCommand create, RemoveCommand remove, ListCommand list, InitializeCommand init, IHubClient client) : base("Vega CLI - Manage Vega development environment")
8+
public AppCommand(CreateCommand create, RemoveCommand remove, ListCommand list, InitializeCommand init, UpdateCommand update, IHubClient client) : base("Vega CLI - Manage Vega development environment")
99
{
1010
AddCommand(create);
1111
AddCommand(remove);
1212
AddCommand(list);
1313
AddCommand(init);
14+
AddCommand(update);
1415
}
1516
}

cli/src/Vdk/Commands/CreateClusterCommand.cs

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
using KubeOps.KubernetesClient;
12
using System.CommandLine;
23
using System.IO.Abstractions;
4+
using k8s.Models;
35
using Vdk.Constants;
4-
using Vdk.Data;
56
using Vdk.Models;
67
using Vdk.Services;
78
using IConsole = Vdk.Services.IConsole;
@@ -11,23 +12,39 @@ namespace Vdk.Commands;
1112
public class CreateClusterCommand : Command
1213
{
1314
private readonly IConsole _console;
14-
private readonly IEmbeddedDataReader _dataReader;
15+
private readonly IKindVersionInfoService _kindVersionInfo;
1516
private readonly IYamlObjectSerializer _yaml;
1617
private readonly IFileSystem _fileSystem;
1718
private readonly IKindClient _kind;
19+
private readonly IHubClient _hub;
1820
private readonly IFluxClient _flux;
1921
private readonly IReverseProxyClient _reverseProxy;
22+
private readonly Func<string, IKubernetesClient> _clientFunc;
23+
private readonly GlobalConfiguration _configs;
2024

21-
public CreateClusterCommand(IConsole console, IEmbeddedDataReader dataReader, IYamlObjectSerializer yaml, IFileSystem fileSystem, IKindClient kind, IFluxClient flux, IReverseProxyClient reverseProxy)
25+
public CreateClusterCommand(
26+
IConsole console,
27+
IKindVersionInfoService kindVersionInfo,
28+
IYamlObjectSerializer yaml,
29+
IFileSystem fileSystem,
30+
IKindClient kind,
31+
IHubClient hub,
32+
IFluxClient flux,
33+
IReverseProxyClient reverseProxy,
34+
Func<string, IKubernetesClient> clientFunc,
35+
GlobalConfiguration configs)
2236
: base("cluster", "Create a Vega development cluster")
2337
{
2438
_console = console;
25-
_dataReader = dataReader;
39+
_kindVersionInfo = kindVersionInfo;
2640
_yaml = yaml;
2741
_fileSystem = fileSystem;
2842
_kind = kind;
43+
_hub = hub;
2944
_flux = flux;
3045
_reverseProxy = reverseProxy;
46+
_clientFunc = clientFunc;
47+
_configs = configs;
3148
var nameOption = new Option<string>(new[] { "-n", "--Name" }, () => Defaults.ClusterName, "The name of the kind cluster to create.");
3249
var controlNodes = new Option<int>(new[] { "-c", "--ControlPlaneNodes" }, () => Defaults.ControlPlaneNodes, "The number of control plane nodes in the cluster.");
3350
var workers = new Option<int>(new[] { "-w", "--Workers" }, () => Defaults.WorkerNodes, "The number of worker nodes in the cluster.");
@@ -39,9 +56,15 @@ public CreateClusterCommand(IConsole console, IEmbeddedDataReader dataReader, IY
3956
this.SetHandler(InvokeAsync, nameOption, controlNodes, workers, kubeVersion);
4057
}
4158

42-
public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPlaneNodes = 1, int workerNodes = 2, string kubeVersion = Defaults.KubeApiVersion)
59+
public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPlaneNodes = 1, int workerNodes = 2, string? kubeVersionRequested = null)
4360
{
44-
var map = _dataReader.ReadJsonObjects<KindVersionMap>("Vdk.Data.KindVersionData.json");
61+
// check if the hub and proxy are there
62+
if (!_reverseProxy.Exists())
63+
_reverseProxy.Create();
64+
if (!_hub.Exists())
65+
_hub.Create();
66+
67+
var map = await _kindVersionInfo.GetVersionInfoAsync();
4568
string? kindVersion = null;
4669
try
4770
{
@@ -52,13 +75,23 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla
5275
_console.WriteError($"Unable to retrieve the installed kind version. {ex.Message}");
5376
return;
5477
}
55-
5678
if (string.IsNullOrWhiteSpace(kindVersion))
5779
{
5880
_console.WriteWarning($"Kind version {kindVersion} is not supported by the current VDK.");
5981
return;
6082
}
83+
var kubeVersion = kubeVersionRequested ?? await _kindVersionInfo.GetDefaultKubernetesVersionAsync(kindVersion);
6184
var image = map.FindImage(kindVersion, kubeVersion);
85+
if (image is null)
86+
{
87+
// If image is null the most likely cause is that the user has a newly released version of kind and
88+
// we have not yet downloaded the latest version info. To resolve this, we will attempt to circumvent
89+
// the cache timeout by directly calling UpdateAsync() and reloading the map. If it still doesn't
90+
// find it, then we are truly in an error state.
91+
await _kindVersionInfo.UpdateAsync();
92+
map = await _kindVersionInfo.GetVersionInfoAsync();
93+
image = map.FindImage(kindVersion, kubeVersion);
94+
}
6295
if (image == null)
6396
{
6497
if (map.FirstOrDefault(m => m.Version == kindVersion) is null)
@@ -85,7 +118,7 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla
85118
{
86119
new()
87120
{
88-
HostPath = _fileSystem.FileInfo.New("ConfigMounts/hosts.toml").FullName,
121+
HostPath = _fileSystem.FileInfo.New("ConfigMounts/hosts.toml").FullName,
89122
ContainerPath = "/etc/containerd/certs.d/hub.dev-k8s.cloud/hosts.toml"
90123
}
91124
};
@@ -94,7 +127,11 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla
94127

95128
for (int index = 0; index < workerNodes; index++)
96129
{
97-
cluster.Nodes.Add(new KindNode() { Role = "worker", Image = image, ExtraMounts = new List<Mount>
130+
cluster.Nodes.Add(new KindNode()
131+
{
132+
Role = "worker",
133+
Image = image,
134+
ExtraMounts = new List<Mount>
98135
{
99136
new()
100137
{
@@ -107,7 +144,7 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla
107144

108145
// add the containerd config patches
109146
cluster.ContainerdConfigPatches =
110-
[ kindVersion == "0.27.0" ?
147+
[ kindVersion == "0.27.0" ?
111148
@"
112149
[plugins.""io.containerd.cri.v1.images"".registry]
113150
config_path = ""/etc/containerd/certs.d""
@@ -120,13 +157,25 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla
120157
];
121158

122159
var manifest = _yaml.Serialize(cluster);
123-
var path = _fileSystem.Path.GetTempFileName();
160+
var path = _fileSystem.Path.Combine(_fileSystem.Path.GetTempPath(), _fileSystem.Path.GetRandomFileName());
124161
await using (var writer = _fileSystem.File.CreateText(path))
125162
{
126163
// _console.WriteLine(path);
127164
await writer.WriteAsync(manifest);
128165
}
129166

167+
// if the name is not provided, and the default cluster name is used.. iterate the clusters to find the next available name
168+
if (string.IsNullOrWhiteSpace(name) || name.ToLower() == Defaults.ClusterName)
169+
{
170+
var clusters = _kind.ListClusters();
171+
var i = 1;
172+
while (clusters.Any(x => x.name.Equals(name, StringComparison.OrdinalIgnoreCase)))
173+
{
174+
name = $"{Defaults.ClusterName}-{i}";
175+
i++;
176+
}
177+
}
178+
130179
_kind.CreateCluster(name.ToLower(), path);
131180
var masterNode = cluster.Nodes.FirstOrDefault(x => x.ExtraPortMappings?.Any() == true);
132181
if (masterNode == null)
@@ -140,6 +189,9 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla
140189
{
141190
_reverseProxy.UpsertCluster(name.ToLower(), masterNode.ExtraPortMappings.First().HostPort,
142191
masterNode.ExtraPortMappings.Last().HostPort);
192+
var ns = _clientFunc(name.ToLower()).Get<V1Namespace>("vega-system");
193+
ns.EnsureMetadata().EnsureAnnotations()[_configs.MasterNodeAnnotation] = _yaml.Serialize(masterNode);
194+
_clientFunc(name.ToLower()).Update(ns);
143195
}
144196
catch (Exception e)
145197
{
@@ -148,6 +200,5 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla
148200
_console.WriteError("Failed to update reverse proxy: " + e.Message);
149201
throw e;
150202
}
151-
152203
}
153204
}

cli/src/Vdk/Commands/InitializeCommand.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ public class InitializeCommand : Command
1212
private readonly CreateRegistryCommand _createRegistry;
1313
private readonly IKindClient _kind;
1414
private readonly IConsole _console;
15+
private readonly IKindVersionInfoService _kindVersionInfo;
1516

16-
public InitializeCommand(CreateClusterCommand createCluster, CreateProxyCommand createProxy, CreateRegistryCommand createRegistry, IKindClient kind, IConsole console)
17+
public InitializeCommand(CreateClusterCommand createCluster, CreateProxyCommand createProxy, CreateRegistryCommand createRegistry,
18+
IKindClient kind, IConsole console, IKindVersionInfoService kindVersionInfo)
1719
: base("init", "Initialize environment")
1820
{
1921
_createCluster = createCluster;
2022
_createProxy = createProxy;
2123
_createRegistry = createRegistry;
2224
_kind = kind;
2325
_console = console;
26+
_kindVersionInfo = kindVersionInfo;
2427
this.SetHandler(InvokeAsync);
2528
}
2629

@@ -29,9 +32,15 @@ public async Task InvokeAsync()
2932
var name = Defaults.ClusterName;
3033
var controlPlaneNodes = 1;
3134
var workerNodes = 2;
32-
var kubeVersion = Defaults.KubeApiVersion;
35+
var kindVersion = _kind.GetVersion();
36+
if (kindVersion is null)
37+
{
38+
_console.WriteWarning("Unable to detect kind version. Please ensure kind is installed in your environment");
39+
return;
40+
}
41+
var kubeVersion = await _kindVersionInfo.GetDefaultKubernetesVersionAsync(kindVersion);
3342

34-
var existing = _kind.ListClusters();
43+
var existing = _kind.ListClusters().Select(x => x.name);
3544
if (existing.Any(x => x.Equals(name, StringComparison.CurrentCultureIgnoreCase)))
3645
{
3746
return;

cli/src/Vdk/Commands/ListClustersCommand.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Vdk.Commands;
66

7-
public class ListClustersCommand: Command
7+
public class ListClustersCommand : Command
88
{
99
private readonly IConsole _console;
1010
private readonly IKindClient _client;
@@ -19,7 +19,8 @@ public ListClustersCommand(IConsole console, IKindClient client) : base("cluster
1919
public Task InvokeAsync()
2020
{
2121
var clusters = _client.ListClusters();
22-
clusters.ForEach(_console.WriteLine);
22+
foreach (var c in clusters.Where(x => x.isVdk))
23+
_console.WriteLine(c.name);
2324
return Task.CompletedTask;
2425
}
2526
}

cli/src/Vdk/Commands/ListCommand.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ namespace Vdk.Commands;
44

55
public class ListCommand: Command
66
{
7-
public ListCommand(ListClustersCommand clustersCommand) : base("list", "List Vega development resources")
7+
public ListCommand(ListClustersCommand clustersCommand, ListKubernetesVersions kubernetesVersions) : base("list", "List Vega development resources")
88
{
99
AddCommand(clustersCommand);
10+
AddCommand(kubernetesVersions);
1011
}
1112
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.CommandLine;
2+
using Vdk.Services;
3+
using IConsole = Vdk.Services.IConsole;
4+
5+
namespace Vdk.Commands;
6+
7+
public class ListKubernetesVersions: Command
8+
{
9+
private readonly IConsole _console;
10+
private readonly IKindClient _client;
11+
private readonly IKindVersionInfoService _versionInfo;
12+
13+
public ListKubernetesVersions(IConsole console, IKindClient client, IKindVersionInfoService versionInfo)
14+
: base("kubernetes-versions", "List available kubernetes versions")
15+
{
16+
_console = console;
17+
_client = client;
18+
_versionInfo = versionInfo;
19+
this.SetHandler(InvokeAsync);
20+
}
21+
22+
public async Task InvokeAsync()
23+
{
24+
var kindVersion = _client.GetVersion();
25+
var map = await _versionInfo.GetVersionInfoAsync();
26+
var current =
27+
map.SingleOrDefault(x => x.Version.Equals(kindVersion, StringComparison.CurrentCultureIgnoreCase));
28+
if (current is not null)
29+
{
30+
foreach (var image in current.Images.OrderByDescending(x=>x.SemanticVersion))
31+
{
32+
_console.WriteLine(image.Version);
33+
}
34+
}
35+
else
36+
{
37+
_console.WriteWarning("No kubernetes versions found for kind version {version}", kindVersion ?? "[None]");
38+
}
39+
}
40+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.CommandLine;
2+
3+
namespace Vdk.Commands;
4+
5+
public class UpdateCommand: Command
6+
{
7+
public UpdateCommand(UpdateKindVersionInfoCommand updateKindVersionInfo) : base("update",
8+
"Update resources in vega development environment")
9+
{
10+
AddCommand(updateKindVersionInfo);
11+
}
12+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.CommandLine;
2+
using Vdk.Services;
3+
4+
namespace Vdk.Commands;
5+
6+
public class UpdateKindVersionInfoCommand: Command
7+
{
8+
private readonly IKindVersionInfoService _client;
9+
10+
public UpdateKindVersionInfoCommand(IKindVersionInfoService client) : base("kind-version-info",
11+
"Update kind version info (Maps kind and Kubernetes versions/enables new releases of kubernetes in vega)")
12+
{
13+
_client = client;
14+
this.SetHandler(InvokeAsync);
15+
}
16+
17+
public Task InvokeAsync()
18+
{
19+
return _client.UpdateAsync();
20+
}
21+
}

cli/src/Vdk/Constants/Defaults.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ public static class Defaults
1515
public const string KubeApiVersion = "1.32";
1616
public const int ControlPlaneNodes = 1;
1717
public const int WorkerNodes = 2;
18+
19+
public const string ConfigDirectoryName = "config";
20+
public const string KindVersionInfoFileName = "kind.version.info.json";
21+
public const int KindVersionInfoCacheMinutes = 1440;
1822
}

cli/src/Vdk/Data/EmbeddedDataReader.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Reflection;
12
using Vdk.Services;
23

34
namespace Vdk.Data;
@@ -11,13 +12,21 @@ public EmbeddedDataReader(IJsonObjectSerializer serializer)
1112
_serializer = serializer;
1213
}
1314

15+
public EmbeddedDataReader(IJsonObjectSerializer serializer, Type refType)
16+
{
17+
_serializer = serializer;
18+
_refAssembly = refType.Assembly;
19+
}
20+
21+
private readonly Assembly _refAssembly = typeof(EmbeddedDataReader).Assembly;
22+
1423
public static IEmbeddedDataReader Default => new EmbeddedDataReader(new JsonObjectSerializer());
1524

1625
public string Read(string path)
1726
{
18-
var names = typeof(EmbeddedDataReader).Assembly.GetManifestResourceNames();
27+
var names = _refAssembly.GetManifestResourceNames();
1928
var name = names.FirstOrDefault(x=>x.Equals(path, StringComparison.CurrentCultureIgnoreCase))??path;
20-
using (var stream = (typeof(EmbeddedDataReader).Assembly.GetManifestResourceStream(name)))
29+
using (var stream = (_refAssembly.GetManifestResourceStream(name)))
2130
{
2231
if(stream == null) throw new FileNotFoundException();
2332
using (var reader = new StreamReader(stream))

0 commit comments

Comments
 (0)