Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cli/src/Vdk/Commands/AppCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ namespace Vdk.Commands;

public class AppCommand: RootCommand
{
public AppCommand(CreateCommand create, RemoveCommand remove, ListCommand list, InitializeCommand init, IHubClient client) : base("Vega CLI - Manage Vega development environment")
public AppCommand(CreateCommand create, RemoveCommand remove, ListCommand list, InitializeCommand init, UpdateCommand update, IHubClient client) : base("Vega CLI - Manage Vega development environment")
{
AddCommand(create);
AddCommand(remove);
AddCommand(list);
AddCommand(init);
AddCommand(update);
}
}
75 changes: 63 additions & 12 deletions cli/src/Vdk/Commands/CreateClusterCommand.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using KubeOps.KubernetesClient;
using System.CommandLine;
using System.IO.Abstractions;
using k8s.Models;
using Vdk.Constants;
using Vdk.Data;
using Vdk.Models;
using Vdk.Services;
using IConsole = Vdk.Services.IConsole;
Expand All @@ -11,23 +12,39 @@
public class CreateClusterCommand : Command
{
private readonly IConsole _console;
private readonly IEmbeddedDataReader _dataReader;
private readonly IKindVersionInfoService _kindVersionInfo;
private readonly IYamlObjectSerializer _yaml;
private readonly IFileSystem _fileSystem;
private readonly IKindClient _kind;
private readonly IHubClient _hub;
private readonly IFluxClient _flux;
private readonly IReverseProxyClient _reverseProxy;
private readonly Func<string, IKubernetesClient> _clientFunc;
private readonly GlobalConfiguration _configs;

public CreateClusterCommand(IConsole console, IEmbeddedDataReader dataReader, IYamlObjectSerializer yaml, IFileSystem fileSystem, IKindClient kind, IFluxClient flux, IReverseProxyClient reverseProxy)
public CreateClusterCommand(
IConsole console,
IKindVersionInfoService kindVersionInfo,
IYamlObjectSerializer yaml,
IFileSystem fileSystem,
IKindClient kind,
IHubClient hub,
IFluxClient flux,
IReverseProxyClient reverseProxy,
Func<string, IKubernetesClient> clientFunc,
GlobalConfiguration configs)
: base("cluster", "Create a Vega development cluster")
{
_console = console;
_dataReader = dataReader;
_kindVersionInfo = kindVersionInfo;
_yaml = yaml;
_fileSystem = fileSystem;
_kind = kind;
_hub = hub;
_flux = flux;
_reverseProxy = reverseProxy;
_clientFunc = clientFunc;
_configs = configs;
var nameOption = new Option<string>(new[] { "-n", "--Name" }, () => Defaults.ClusterName, "The name of the kind cluster to create.");
var controlNodes = new Option<int>(new[] { "-c", "--ControlPlaneNodes" }, () => Defaults.ControlPlaneNodes, "The number of control plane nodes in the cluster.");
var workers = new Option<int>(new[] { "-w", "--Workers" }, () => Defaults.WorkerNodes, "The number of worker nodes in the cluster.");
Expand All @@ -39,9 +56,15 @@
this.SetHandler(InvokeAsync, nameOption, controlNodes, workers, kubeVersion);
}

public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPlaneNodes = 1, int workerNodes = 2, string kubeVersion = Defaults.KubeApiVersion)
public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPlaneNodes = 1, int workerNodes = 2, string? kubeVersionRequested = null)
{
var map = _dataReader.ReadJsonObjects<KindVersionMap>("Vdk.Data.KindVersionData.json");
// check if the hub and proxy are there
if (!_reverseProxy.Exists())
_reverseProxy.Create();
if (!_hub.Exists())
_hub.Create();

var map = await _kindVersionInfo.GetVersionInfoAsync();
string? kindVersion = null;
try
{
Expand All @@ -52,13 +75,23 @@
_console.WriteError($"Unable to retrieve the installed kind version. {ex.Message}");
return;
}

if (string.IsNullOrWhiteSpace(kindVersion))
{
_console.WriteWarning($"Kind version {kindVersion} is not supported by the current VDK.");
return;
}
var kubeVersion = kubeVersionRequested ?? await _kindVersionInfo.GetDefaultKubernetesVersionAsync(kindVersion);
var image = map.FindImage(kindVersion, kubeVersion);
if (image is null)
Copy link

Copilot AI May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider adding additional logging or clarifying comments in the fallback block to detail the recovery process when the image remains null after attempting an update.

Copilot uses AI. Check for mistakes.
{
// If image is null the most likely cause is that the user has a newly released version of kind and
// we have not yet downloaded the latest version info. To resolve this, we will attempt to circumvent
// the cache timeout by directly calling UpdateAsync() and reloading the map. If it still doesn't
// find it, then we are truly in an error state.
await _kindVersionInfo.UpdateAsync();
map = await _kindVersionInfo.GetVersionInfoAsync();
image = map.FindImage(kindVersion, kubeVersion);
}
if (image == null)
{
if (map.FirstOrDefault(m => m.Version == kindVersion) is null)
Expand All @@ -85,7 +118,7 @@
{
new()
{
HostPath = _fileSystem.FileInfo.New("ConfigMounts/hosts.toml").FullName,
HostPath = _fileSystem.FileInfo.New("ConfigMounts/hosts.toml").FullName,
ContainerPath = "/etc/containerd/certs.d/hub.dev-k8s.cloud/hosts.toml"
}
};
Expand All @@ -94,7 +127,11 @@

for (int index = 0; index < workerNodes; index++)
{
cluster.Nodes.Add(new KindNode() { Role = "worker", Image = image, ExtraMounts = new List<Mount>
cluster.Nodes.Add(new KindNode()
{
Role = "worker",
Image = image,
ExtraMounts = new List<Mount>
{
new()
{
Expand All @@ -107,7 +144,7 @@

// add the containerd config patches
cluster.ContainerdConfigPatches =
[ kindVersion == "0.27.0" ?
[ kindVersion == "0.27.0" ?
@"
[plugins.""io.containerd.cri.v1.images"".registry]
config_path = ""/etc/containerd/certs.d""
Expand All @@ -120,13 +157,25 @@
];

var manifest = _yaml.Serialize(cluster);
var path = _fileSystem.Path.GetTempFileName();
var path = _fileSystem.Path.Combine(_fileSystem.Path.GetTempPath(), _fileSystem.Path.GetRandomFileName());
await using (var writer = _fileSystem.File.CreateText(path))
{
// _console.WriteLine(path);
await writer.WriteAsync(manifest);
}

// if the name is not provided, and the default cluster name is used.. iterate the clusters to find the next available name
if (string.IsNullOrWhiteSpace(name) || name.ToLower() == Defaults.ClusterName)
{
var clusters = _kind.ListClusters();
var i = 1;
while (clusters.Any(x => x.name.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
name = $"{Defaults.ClusterName}-{i}";
i++;
}
}

_kind.CreateCluster(name.ToLower(), path);
var masterNode = cluster.Nodes.FirstOrDefault(x => x.ExtraPortMappings?.Any() == true);
if (masterNode == null)
Expand All @@ -138,16 +187,18 @@
_flux.Bootstrap(name.ToLower(), "./clusters/default", branch: "main");
try
{
_reverseProxy.UpsertCluster(name.ToLower(), masterNode.ExtraPortMappings.First().HostPort,

Check warning on line 190 in cli/src/Vdk/Commands/CreateClusterCommand.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'source' in 'PortMapping Enumerable.First<PortMapping>(IEnumerable<PortMapping> source)'.
masterNode.ExtraPortMappings.Last().HostPort);
var ns = _clientFunc(name.ToLower()).Get<V1Namespace>("vega-system");
ns.EnsureMetadata().EnsureAnnotations()[_configs.MasterNodeAnnotation] = _yaml.Serialize(masterNode);
_clientFunc(name.ToLower()).Update(ns);

Check warning on line 194 in cli/src/Vdk/Commands/CreateClusterCommand.cs

View workflow job for this annotation

GitHub Actions / build

The type 'k8s.Models.V1Namespace?' cannot be used as type parameter 'TEntity' in the generic type or method 'IKubernetesClient.Update<TEntity>(TEntity)'. Nullability of type argument 'k8s.Models.V1Namespace?' doesn't match constraint type 'k8s.IKubernetesObject<k8s.Models.V1ObjectMeta>'.
}
catch (Exception e)
{
// print the stack trace
_console.WriteLine(e.StackTrace);

Check warning on line 199 in cli/src/Vdk/Commands/CreateClusterCommand.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'message' in 'void IConsole.WriteLine(string message)'.
_console.WriteError("Failed to update reverse proxy: " + e.Message);
throw e;
}

}
}
15 changes: 12 additions & 3 deletions cli/src/Vdk/Commands/InitializeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ public class InitializeCommand : Command
private readonly CreateRegistryCommand _createRegistry;
private readonly IKindClient _kind;
private readonly IConsole _console;
private readonly IKindVersionInfoService _kindVersionInfo;

public InitializeCommand(CreateClusterCommand createCluster, CreateProxyCommand createProxy, CreateRegistryCommand createRegistry, IKindClient kind, IConsole console)
public InitializeCommand(CreateClusterCommand createCluster, CreateProxyCommand createProxy, CreateRegistryCommand createRegistry,
IKindClient kind, IConsole console, IKindVersionInfoService kindVersionInfo)
: base("init", "Initialize environment")
{
_createCluster = createCluster;
_createProxy = createProxy;
_createRegistry = createRegistry;
_kind = kind;
_console = console;
_kindVersionInfo = kindVersionInfo;
this.SetHandler(InvokeAsync);
}

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

var existing = _kind.ListClusters();
var existing = _kind.ListClusters().Select(x => x.name);
if (existing.Any(x => x.Equals(name, StringComparison.CurrentCultureIgnoreCase)))
{
return;
Expand Down
5 changes: 3 additions & 2 deletions cli/src/Vdk/Commands/ListClustersCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Vdk.Commands;

public class ListClustersCommand: Command
public class ListClustersCommand : Command
{
private readonly IConsole _console;
private readonly IKindClient _client;
Expand All @@ -19,7 +19,8 @@ public ListClustersCommand(IConsole console, IKindClient client) : base("cluster
public Task InvokeAsync()
{
var clusters = _client.ListClusters();
clusters.ForEach(_console.WriteLine);
foreach (var c in clusters.Where(x => x.isVdk))
_console.WriteLine(c.name);
return Task.CompletedTask;
}
}
3 changes: 2 additions & 1 deletion cli/src/Vdk/Commands/ListCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ namespace Vdk.Commands;

public class ListCommand: Command
{
public ListCommand(ListClustersCommand clustersCommand) : base("list", "List Vega development resources")
public ListCommand(ListClustersCommand clustersCommand, ListKubernetesVersions kubernetesVersions) : base("list", "List Vega development resources")
{
AddCommand(clustersCommand);
AddCommand(kubernetesVersions);
}
}
40 changes: 40 additions & 0 deletions cli/src/Vdk/Commands/ListKubernetesVersions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.CommandLine;
using Vdk.Services;
using IConsole = Vdk.Services.IConsole;

namespace Vdk.Commands;

public class ListKubernetesVersions: Command
{
private readonly IConsole _console;
private readonly IKindClient _client;
private readonly IKindVersionInfoService _versionInfo;

public ListKubernetesVersions(IConsole console, IKindClient client, IKindVersionInfoService versionInfo)
: base("kubernetes-versions", "List available kubernetes versions")
{
_console = console;
_client = client;
_versionInfo = versionInfo;
this.SetHandler(InvokeAsync);
}

public async Task InvokeAsync()
{
var kindVersion = _client.GetVersion();
var map = await _versionInfo.GetVersionInfoAsync();
var current =
map.SingleOrDefault(x => x.Version.Equals(kindVersion, StringComparison.CurrentCultureIgnoreCase));
if (current is not null)
{
foreach (var image in current.Images.OrderByDescending(x=>x.SemanticVersion))
{
_console.WriteLine(image.Version);
}
}
else
{
_console.WriteWarning("No kubernetes versions found for kind version {version}", kindVersion ?? "[None]");
}
}
}
12 changes: 12 additions & 0 deletions cli/src/Vdk/Commands/UpdateCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.CommandLine;

namespace Vdk.Commands;

public class UpdateCommand: Command
{
public UpdateCommand(UpdateKindVersionInfoCommand updateKindVersionInfo) : base("update",
"Update resources in vega development environment")
{
AddCommand(updateKindVersionInfo);
}
}
21 changes: 21 additions & 0 deletions cli/src/Vdk/Commands/UpdateKindVersionInfoCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.CommandLine;
using Vdk.Services;

namespace Vdk.Commands;

public class UpdateKindVersionInfoCommand: Command
{
private readonly IKindVersionInfoService _client;

public UpdateKindVersionInfoCommand(IKindVersionInfoService client) : base("kind-version-info",
"Update kind version info (Maps kind and Kubernetes versions/enables new releases of kubernetes in vega)")
{
_client = client;
this.SetHandler(InvokeAsync);
}

public Task InvokeAsync()
{
return _client.UpdateAsync();
}
}
4 changes: 4 additions & 0 deletions cli/src/Vdk/Constants/Defaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ public static class Defaults
public const string KubeApiVersion = "1.32";
public const int ControlPlaneNodes = 1;
public const int WorkerNodes = 2;

public const string ConfigDirectoryName = "config";
public const string KindVersionInfoFileName = "kind.version.info.json";
public const int KindVersionInfoCacheMinutes = 1440;
}
13 changes: 11 additions & 2 deletions cli/src/Vdk/Data/EmbeddedDataReader.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Reflection;
using Vdk.Services;

namespace Vdk.Data;
Expand All @@ -11,13 +12,21 @@ public EmbeddedDataReader(IJsonObjectSerializer serializer)
_serializer = serializer;
}

public EmbeddedDataReader(IJsonObjectSerializer serializer, Type refType)
{
_serializer = serializer;
_refAssembly = refType.Assembly;
}

private readonly Assembly _refAssembly = typeof(EmbeddedDataReader).Assembly;

public static IEmbeddedDataReader Default => new EmbeddedDataReader(new JsonObjectSerializer());

public string Read(string path)
{
var names = typeof(EmbeddedDataReader).Assembly.GetManifestResourceNames();
var names = _refAssembly.GetManifestResourceNames();
var name = names.FirstOrDefault(x=>x.Equals(path, StringComparison.CurrentCultureIgnoreCase))??path;
using (var stream = (typeof(EmbeddedDataReader).Assembly.GetManifestResourceStream(name)))
using (var stream = (_refAssembly.GetManifestResourceStream(name)))
{
if(stream == null) throw new FileNotFoundException();
using (var reader = new StreamReader(stream))
Expand Down
Loading
Loading