diff --git a/cli/src/Vdk/Commands/AppCommand.cs b/cli/src/Vdk/Commands/AppCommand.cs index 224a367..b1b06ac 100644 --- a/cli/src/Vdk/Commands/AppCommand.cs +++ b/cli/src/Vdk/Commands/AppCommand.cs @@ -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); } } \ No newline at end of file diff --git a/cli/src/Vdk/Commands/CreateClusterCommand.cs b/cli/src/Vdk/Commands/CreateClusterCommand.cs index 5391d9e..5d0b860 100644 --- a/cli/src/Vdk/Commands/CreateClusterCommand.cs +++ b/cli/src/Vdk/Commands/CreateClusterCommand.cs @@ -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; @@ -11,23 +12,39 @@ namespace Vdk.Commands; 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 _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 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(new[] { "-n", "--Name" }, () => Defaults.ClusterName, "The name of the kind cluster to create."); var controlNodes = new Option(new[] { "-c", "--ControlPlaneNodes" }, () => Defaults.ControlPlaneNodes, "The number of control plane nodes in the cluster."); var workers = new Option(new[] { "-w", "--Workers" }, () => Defaults.WorkerNodes, "The number of worker nodes in the cluster."); @@ -39,9 +56,15 @@ public CreateClusterCommand(IConsole console, IEmbeddedDataReader dataReader, IY 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("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 { @@ -52,13 +75,23 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla _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) + { + // 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) @@ -85,7 +118,7 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla { 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" } }; @@ -94,7 +127,11 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla for (int index = 0; index < workerNodes; index++) { - cluster.Nodes.Add(new KindNode() { Role = "worker", Image = image, ExtraMounts = new List + cluster.Nodes.Add(new KindNode() + { + Role = "worker", + Image = image, + ExtraMounts = new List { new() { @@ -107,7 +144,7 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla // 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"" @@ -120,13 +157,25 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla ]; 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) @@ -140,6 +189,9 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla { _reverseProxy.UpsertCluster(name.ToLower(), masterNode.ExtraPortMappings.First().HostPort, masterNode.ExtraPortMappings.Last().HostPort); + var ns = _clientFunc(name.ToLower()).Get("vega-system"); + ns.EnsureMetadata().EnsureAnnotations()[_configs.MasterNodeAnnotation] = _yaml.Serialize(masterNode); + _clientFunc(name.ToLower()).Update(ns); } catch (Exception e) { @@ -148,6 +200,5 @@ public async Task InvokeAsync(string name = Defaults.ClusterName, int controlPla _console.WriteError("Failed to update reverse proxy: " + e.Message); throw e; } - } } \ No newline at end of file diff --git a/cli/src/Vdk/Commands/InitializeCommand.cs b/cli/src/Vdk/Commands/InitializeCommand.cs index 30d0c8e..d6dad79 100644 --- a/cli/src/Vdk/Commands/InitializeCommand.cs +++ b/cli/src/Vdk/Commands/InitializeCommand.cs @@ -12,8 +12,10 @@ 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; @@ -21,6 +23,7 @@ public InitializeCommand(CreateClusterCommand createCluster, CreateProxyCommand _createRegistry = createRegistry; _kind = kind; _console = console; + _kindVersionInfo = kindVersionInfo; this.SetHandler(InvokeAsync); } @@ -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; diff --git a/cli/src/Vdk/Commands/ListClustersCommand.cs b/cli/src/Vdk/Commands/ListClustersCommand.cs index bee4a4b..3e9fca6 100644 --- a/cli/src/Vdk/Commands/ListClustersCommand.cs +++ b/cli/src/Vdk/Commands/ListClustersCommand.cs @@ -4,7 +4,7 @@ namespace Vdk.Commands; -public class ListClustersCommand: Command +public class ListClustersCommand : Command { private readonly IConsole _console; private readonly IKindClient _client; @@ -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; } } \ No newline at end of file diff --git a/cli/src/Vdk/Commands/ListCommand.cs b/cli/src/Vdk/Commands/ListCommand.cs index 99fa008..781b2a2 100644 --- a/cli/src/Vdk/Commands/ListCommand.cs +++ b/cli/src/Vdk/Commands/ListCommand.cs @@ -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); } } \ No newline at end of file diff --git a/cli/src/Vdk/Commands/ListKubernetesVersions.cs b/cli/src/Vdk/Commands/ListKubernetesVersions.cs new file mode 100644 index 0000000..dc50037 --- /dev/null +++ b/cli/src/Vdk/Commands/ListKubernetesVersions.cs @@ -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]"); + } + } +} \ No newline at end of file diff --git a/cli/src/Vdk/Commands/UpdateCommand.cs b/cli/src/Vdk/Commands/UpdateCommand.cs new file mode 100644 index 0000000..a4fb798 --- /dev/null +++ b/cli/src/Vdk/Commands/UpdateCommand.cs @@ -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); + } +} \ No newline at end of file diff --git a/cli/src/Vdk/Commands/UpdateKindVersionInfoCommand.cs b/cli/src/Vdk/Commands/UpdateKindVersionInfoCommand.cs new file mode 100644 index 0000000..16e73c2 --- /dev/null +++ b/cli/src/Vdk/Commands/UpdateKindVersionInfoCommand.cs @@ -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(); + } +} \ No newline at end of file diff --git a/cli/src/Vdk/Constants/Defaults.cs b/cli/src/Vdk/Constants/Defaults.cs index 2626bd4..62da16a 100644 --- a/cli/src/Vdk/Constants/Defaults.cs +++ b/cli/src/Vdk/Constants/Defaults.cs @@ -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; } \ No newline at end of file diff --git a/cli/src/Vdk/Data/EmbeddedDataReader.cs b/cli/src/Vdk/Data/EmbeddedDataReader.cs index 26fcad1..d594681 100644 --- a/cli/src/Vdk/Data/EmbeddedDataReader.cs +++ b/cli/src/Vdk/Data/EmbeddedDataReader.cs @@ -1,3 +1,4 @@ +using System.Reflection; using Vdk.Services; namespace Vdk.Data; @@ -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)) diff --git a/cli/src/Vdk/GlobalConfiguration.cs b/cli/src/Vdk/GlobalConfiguration.cs new file mode 100644 index 0000000..336eea9 --- /dev/null +++ b/cli/src/Vdk/GlobalConfiguration.cs @@ -0,0 +1,24 @@ +using Vdk.Constants; + +namespace Vdk; + +public class GlobalConfiguration +{ + private string? _profileDirectory = null; + + public string ConfigDirectoryName { get; set; } = Defaults.ConfigDirectoryName; + + public string VegaDirectory + { + get + { + return _profileDirectory ??= + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".vega"); + } + } + + public string ConfigDirectoryPath => Path.Combine(VegaDirectory, ConfigDirectoryName); + public string KindVersionInfoFilePath => Path.Combine(ConfigDirectoryPath, Defaults.KindVersionInfoFileName); + + public string MasterNodeAnnotation = "vdk.vega.io/cluster"; +} \ No newline at end of file diff --git a/cli/src/Vdk/Models/KindCluster.cs b/cli/src/Vdk/Models/KindCluster.cs index 8e81ec8..13e88fe 100644 --- a/cli/src/Vdk/Models/KindCluster.cs +++ b/cli/src/Vdk/Models/KindCluster.cs @@ -1,3 +1,7 @@ +using System.Text.Json.Serialization; +using System.Xml.Serialization; +using YamlDotNet.Serialization; + namespace Vdk.Models; public class KindCluster @@ -17,6 +21,14 @@ public class KindNode public Dictionary? Labels { get; set; } = null; public List? ExtraPortMappings { get; set; } = null; public List? ExtraMounts { get; set; } = null; + + [JsonIgnore] + [YamlIgnore] + public int? HttpsHostPort => ExtraPortMappings?.FirstOrDefault(x => x.ContainerPort == 30443)?.HostPort; + + [YamlIgnore] + [JsonIgnore] + public int? HttpHostPort => ExtraPortMappings?.FirstOrDefault(x => x.ContainerPort == 30080)?.HostPort; } public class FileMapping diff --git a/cli/src/Vdk/Models/KindVersion.cs b/cli/src/Vdk/Models/KindVersion.cs index 25d4d3e..33b2127 100644 --- a/cli/src/Vdk/Models/KindVersion.cs +++ b/cli/src/Vdk/Models/KindVersion.cs @@ -1,12 +1,17 @@ +using System.Text.Json.Serialization; using SemVersion; namespace Vdk.Models; public class KindVersion { + [JsonPropertyName("version")] public string Version { get; set; } = ""; // SemanticVersion.BaseVersion(); + [JsonIgnore] public SemanticVersion SemanticVersion => SemanticVersion.TryParse(Version, out var semver) ? semver : SemanticVersion.BaseVersion(); + + [JsonPropertyName("images")] public List Images { get; set; } = new(); } \ No newline at end of file diff --git a/cli/src/Vdk/Models/KindVersionMap.cs b/cli/src/Vdk/Models/KindVersionMap.cs index 7dde257..7fabe1b 100644 --- a/cli/src/Vdk/Models/KindVersionMap.cs +++ b/cli/src/Vdk/Models/KindVersionMap.cs @@ -2,6 +2,17 @@ namespace Vdk.Models; public class KindVersionMap: List { + public KindVersionMap() + { + } + + public KindVersionMap(IEnumerable versions) + { + AddRange(versions); + } + + public DateTime LastUpdated { get; set; } = DateTime.MinValue; + public string? FindImage(string kindVersion, string kubeVersion) { var image = this.SingleOrDefault(k => k.Version == kindVersion) diff --git a/cli/src/Vdk/Models/KubeImage.cs b/cli/src/Vdk/Models/KubeImage.cs index b6038f5..0e38490 100644 --- a/cli/src/Vdk/Models/KubeImage.cs +++ b/cli/src/Vdk/Models/KubeImage.cs @@ -1,12 +1,17 @@ +using System.Text.Json.Serialization; using SemVersion; namespace Vdk.Models; public class KubeImage { + [JsonPropertyName("version")] public string Version { get; set; } = string.Empty; // SemanticVersion.BaseVersion(); + [JsonIgnore] public SemanticVersion SemanticVersion => SemanticVersion.TryParse(Version, out var semver) ? semver : SemanticVersion.BaseVersion(); + + [JsonPropertyName("image")] public string Image { get; set; } = string.Empty; } \ No newline at end of file diff --git a/cli/src/Vdk/Models/PortMapping.cs b/cli/src/Vdk/Models/PortMapping.cs index 85c0ed0..d4c874d 100644 --- a/cli/src/Vdk/Models/PortMapping.cs +++ b/cli/src/Vdk/Models/PortMapping.cs @@ -25,6 +25,5 @@ internal static int GetRandomUnusedPort() return port; } - public static PortMapping DefaultRegistryPortMapping = new PortMapping { ContainerPort =Containers.RegistryContainerPort, HostPort = Containers.RegistryHostPort }; - + public static PortMapping DefaultRegistryPortMapping = new PortMapping { ContainerPort = Containers.RegistryContainerPort, HostPort = Containers.RegistryHostPort }; } \ No newline at end of file diff --git a/cli/src/Vdk/Properties/launchSettings.json b/cli/src/Vdk/Properties/launchSettings.json index 5cffc4d..462db42 100644 --- a/cli/src/Vdk/Properties/launchSettings.json +++ b/cli/src/Vdk/Properties/launchSettings.json @@ -1,11 +1,20 @@ { "profiles": { - "Vdk": { + "update-kind": { "commandName": "Project", - "commandLineArgs": "create cluster -c 1 -w 3 -k 1.30", - "environmentVariables": { - "GITHUB_TOKEN": "xxxxx" - } + "commandLineArgs": "update kind-version-info" + }, + "remove-proxy": { + "commandName": "Project", + "commandLineArgs": "remove proxy" + }, + "create-proxy": { + "commandName": "Project", + "commandLineArgs": "create proxy" + }, + "create-clutser": { + "commandName": "Project", + "commandLineArgs": "create cluster" }, "WSL": { "commandName": "WSL2", diff --git a/cli/src/Vdk/ServiceProviderBuilder.cs b/cli/src/Vdk/ServiceProviderBuilder.cs index b456d7b..3e73b4c 100644 --- a/cli/src/Vdk/ServiceProviderBuilder.cs +++ b/cli/src/Vdk/ServiceProviderBuilder.cs @@ -7,7 +7,10 @@ using Vdk.Data; using Vdk.Services; using System.Runtime.InteropServices; +using System.Text; +using Octokit; using Vdk.Constants; +using k8s.Exceptions; namespace Vdk; @@ -17,9 +20,9 @@ public static IServiceProvider Build() { var services = new ServiceCollection(); var yaml = new YamlObjectSerializer(); - - _ = services - .AddSingleton(s => + var gitHubClient = new GitHubClient(new ProductHeaderValue("vega-client")); + services + .AddSingleton(s => { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return new(Platform.Linux); if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return new(Platform.OSX); @@ -37,10 +40,14 @@ public static IServiceProvider Build() .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() @@ -49,14 +56,29 @@ public static IServiceProvider Build() .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() - .AddTransient>(provider => + .AddSingleton() + .AddSingleton(gitHubClient) + .AddSingleton(new GlobalConfiguration()) + .AddSingleton>(provider => { + // Static cache for kubeconfig YAMLs by context + Dictionary kubeConfigCache = new(); + object cacheLock = new(); return context => { - var config = KubernetesClientConfiguration.LoadKubeConfig(); - config.CurrentContext = $"kind-{context}"; - return new KubernetesClient(KubernetesClientConfiguration.BuildConfigFromConfigObject(config)); + lock (cacheLock) + { + if (!kubeConfigCache.ContainsKey(context)) + { + //Console.WriteLine($"Fetching kubeconfig for kind context: {context}"); + var shell = provider.GetRequiredService(); + var kubeConfigYaml = shell.ExecuteAndCapture("kind", ["get", "kubeconfig", "-n", $"{context}"]); + using var stream = new MemoryStream(Encoding.Default.GetBytes(kubeConfigYaml)); + var config = KubernetesClientConfiguration.BuildConfigFromConfigFile(stream); + kubeConfigCache[context] = new KubernetesClient(config); + } + return kubeConfigCache[context]; + } }; }) .AddSingleton(provider => diff --git a/cli/src/Vdk/Services/DockerHubClient.cs b/cli/src/Vdk/Services/DockerHubClient.cs index 22bc2a8..856c426 100644 --- a/cli/src/Vdk/Services/DockerHubClient.cs +++ b/cli/src/Vdk/Services/DockerHubClient.cs @@ -3,38 +3,31 @@ namespace Vdk.Services; -public class DockerHubClient : IHubClient +public class DockerHubClient(IDockerEngine docker, IConsole console) : IHubClient { - private readonly IDockerEngine _docker; - private readonly IConsole _console; - - public DockerHubClient(IDockerEngine docker, IConsole console) - { - _docker = docker; - _console = console; - } public void Create() { - if (!_docker.Exists(Containers.RegistryName)) - { - _console.WriteLine("Creating Vega VDK Registry"); - _console.WriteLine(" - This may take a few minutes..."); - _docker.Run(Containers.RegistryImage, - Containers.RegistryName, - [PortMapping.DefaultRegistryPortMapping], - null, - null, - null); - } + if (Exists()) return; + console.WriteLine("Creating Vega VDK Registry"); + console.WriteLine(" - This may take a few minutes..."); + docker.Run(Containers.RegistryImage, + Containers.RegistryName, + [PortMapping.DefaultRegistryPortMapping], + null, + null, + null); } public void Destroy() { - if (_docker.Exists(Containers.RegistryName)) - { - _console.WriteWarning("Deleting Vega VDK Registry from Docker"); - _console.WriteLine("You can recreate the registry using command 'vega create registry'"); - _docker.Delete(Containers.RegistryName); - } + if (!Exists()) return; + console.WriteWarning("Deleting Vega VDK Registry from Docker"); + console.WriteLine("You can recreate the registry using command 'vega create registry'"); + docker.Delete(Containers.RegistryName); + } + + public bool Exists() + { + return docker.Exists(Containers.RegistryName); } } \ No newline at end of file diff --git a/cli/src/Vdk/Services/IHubClient.cs b/cli/src/Vdk/Services/IHubClient.cs index de1938e..68a0839 100644 --- a/cli/src/Vdk/Services/IHubClient.cs +++ b/cli/src/Vdk/Services/IHubClient.cs @@ -3,5 +3,8 @@ namespace Vdk.Services; public interface IHubClient { void Create(); + void Destroy(); + + bool Exists(); } \ No newline at end of file diff --git a/cli/src/Vdk/Services/IKindClient.cs b/cli/src/Vdk/Services/IKindClient.cs index 4941ec9..ad139d0 100644 --- a/cli/src/Vdk/Services/IKindClient.cs +++ b/cli/src/Vdk/Services/IKindClient.cs @@ -1,10 +1,16 @@ +using Vdk.Models; + namespace Vdk.Services; public interface IKindClient { string? GetVersion(); + string GetVersionString(); + void CreateCluster(string name, string configPath); + void DeleteCluster(string name); - List ListClusters(); + + List<(string name, bool isVdk, KindNode? master)> ListClusters(); } \ No newline at end of file diff --git a/cli/src/Vdk/Services/IKindVersionInfoService.cs b/cli/src/Vdk/Services/IKindVersionInfoService.cs new file mode 100644 index 0000000..0953e3e --- /dev/null +++ b/cli/src/Vdk/Services/IKindVersionInfoService.cs @@ -0,0 +1,11 @@ +using Vdk.Models; + +namespace Vdk.Services; + +public interface IKindVersionInfoService +{ + Task UpdateAsync(); + Task GetVersionInfoAsync(); + + Task GetDefaultKubernetesVersionAsync(string kindVersion); +} \ No newline at end of file diff --git a/cli/src/Vdk/Services/IReverseProxyClient.cs b/cli/src/Vdk/Services/IReverseProxyClient.cs index 5683c68..414860f 100644 --- a/cli/src/Vdk/Services/IReverseProxyClient.cs +++ b/cli/src/Vdk/Services/IReverseProxyClient.cs @@ -2,7 +2,7 @@ namespace Vdk.Services; public interface IReverseProxyClient { - void UpsertCluster(string clusterName, int targetPortHttps, int targetPortHttp); + void UpsertCluster(string clusterName, int targetPortHttps, int targetPortHttp, bool reload = true); void DeleteCluster(string clusterName); @@ -11,4 +11,6 @@ public interface IReverseProxyClient void Delete(); void List(); + + bool Exists(); } \ No newline at end of file diff --git a/cli/src/Vdk/Services/KindClient.cs b/cli/src/Vdk/Services/KindClient.cs index c9874fd..37cba85 100644 --- a/cli/src/Vdk/Services/KindClient.cs +++ b/cli/src/Vdk/Services/KindClient.cs @@ -1,32 +1,75 @@ +using k8s.Models; +using KubeOps.KubernetesClient; +using Vdk.Models; + namespace Vdk.Services; public class KindClient : IKindClient { private readonly IConsole _console; private readonly IShell _shell; + private readonly Func _client; + private readonly IYamlObjectSerializer _yaml; + private readonly GlobalConfiguration _configs; - public KindClient(IConsole console, IShell shell) + public KindClient(IConsole console, IShell shell, Func client, IYamlObjectSerializer yaml, GlobalConfiguration configs) { _console = console; _shell = shell; + _client = client; + _yaml = yaml; + _configs = configs; } public void CreateCluster(string name, string configPath) { - _shell.Execute("kind", new[] { "create", "cluster", "--config", configPath, "--name", name.ToLower() }); + _shell.Execute("kind", ["create", "cluster", "--config", configPath, "--name", name.ToLower()]); } public void DeleteCluster(string name) { - _shell.Execute("kind", new [] {"delete", "cluster", "-n", name.ToLower()}); + _shell.Execute("kind", ["delete", "cluster", "-n", name.ToLower()]); } - public List ListClusters() + public List<(string name, bool isVdk, KindNode? master)> ListClusters() { - var response = _shell.ExecuteAndCapture("kind", new[] { "get", "clusters" }); - if (string.IsNullOrWhiteSpace(response)) return new List(); - return response.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()) + var response = _shell.ExecuteAndCapture("kind", ["get", "clusters"]); + if (string.IsNullOrWhiteSpace(response)) return new List<(string name, bool isVdk, KindNode? master)>(); + // Split by new lines and trim each line, removing empty entries + var names = response.Split(Environment.NewLine.ToArray(), StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()) .ToList(); + + // get a client for each one and check to see if its a vdk cluster + var vdkClusters = new List<(string name, bool isVdk, KindNode? master)>(); + foreach (var name in names) + { + try + { + var k8SClient = _client(name); + // check the cluster for the vega-system namespace. If it has it, then go fetch the mapped ports + var ns = k8SClient.Get("vega-system"); + if (ns != null && ns.EnsureMetadata().EnsureAnnotations().ContainsKey(_configs.MasterNodeAnnotation)) + { + try + { + var node = _yaml.Deserialize(ns.Annotations()[_configs.MasterNodeAnnotation]); + vdkClusters.Add(node != null ? (name, true, node) : (name, false, null)); + } + catch (Exception) + { + // couldnt parse + } + } + else + vdkClusters.Add((name, false, null)); + } + catch (Exception ex) + { + _console.WriteLine($"Error checking cluster {name}: {ex.Message}"); + } + } + + return vdkClusters; } public string? GetVersion() @@ -37,6 +80,6 @@ public List ListClusters() public string GetVersionString() { - return _shell.ExecuteAndCapture("kind", new[] { "--version" }); + return _shell.ExecuteAndCapture("kind", ["--version"]); } } \ No newline at end of file diff --git a/cli/src/Vdk/Services/KindVersionInfoService.cs b/cli/src/Vdk/Services/KindVersionInfoService.cs new file mode 100644 index 0000000..fa9c058 --- /dev/null +++ b/cli/src/Vdk/Services/KindVersionInfoService.cs @@ -0,0 +1,158 @@ +using System; +using System.IO.Abstractions; +using Octokit; +using Vdk.Constants; +using Vdk.Data; +using Vdk.Models; + +namespace Vdk.Services; + +public class KindVersionInfoService(IConsole console, IFileSystem fileSystem, IGitHubClient gitHub, IEmbeddedDataReader dataReader, + IJsonObjectSerializer serializer, GlobalConfiguration configuration) : IKindVersionInfoService +{ + + private KindVersionMap? _cache = null; + protected string KindVersionInfoFilePath => configuration.KindVersionInfoFilePath; + + public async Task UpdateAsync() + { + const int count = 100; + try + { + var kindVersions = (await FetchKindVersionsAsync()) + .Where(kv => kv.Images.Count > 0 && !string.IsNullOrWhiteSpace(kv.Version)) + .OrderByDescending(kv => + { + if (Version.TryParse(kv.Version, out var v)) + return v; + // fallback for non-standard version strings + return new Version(0, 0); + }) + .ThenByDescending(kv => kv.Version, StringComparer.OrdinalIgnoreCase) + .Take(count) + .ToList(); + var result = new KindVersionMap(kindVersions) + { + LastUpdated = DateTime.Now + }; + _cache = result; + var json = serializer.Serialize(kindVersions); + var file = fileSystem.FileInfo.New(KindVersionInfoFilePath); + if (!file.Directory!.Exists) + { + console.WriteLine($"Creating config directory '{file.Directory.FullName}'"); + file.Directory.Create(); + } + using (var writer = file.CreateText()) + { + console.WriteLine($"Saving {file.FullName}"); + await writer.WriteAsync(json); + } + + return result; + } + catch (Exception ex) + { + console.WriteWarning($"Error: {ex.Message}"); + return null; + } + } + + public async Task GetVersionInfoAsync() + { + var file = fileSystem.FileInfo.New(KindVersionInfoFilePath); + KindVersionMap? result = _cache; + if (result is null && file.Exists) + { + using (var reader = file.OpenText()) + { + var json = await reader.ReadToEndAsync(); + result = serializer.Deserialize(json); + } + } + + if (result is null || DateTime.Now.Subtract(result.LastUpdated) > TimeSpan.FromMinutes(Defaults.KindVersionInfoCacheMinutes)) + { + result = await UpdateAsync(); + } + + if (result is null) + { + result = dataReader.ReadJsonObjects("Vdk.Data.KindVersionData.json"); + } + + return result; + } + + public async Task GetDefaultKubernetesVersionAsync(string kindVersion) + { + var map = await GetVersionInfoAsync(); + + var kubeVersion = map.SingleOrDefault(x => x.Version.Equals(kindVersion, StringComparison.CurrentCultureIgnoreCase))? + .Images.OrderByDescending(x => x.SemanticVersion).FirstOrDefault(); + + if (kubeVersion == null) return Defaults.KubeApiVersion; + + var version = $"{kubeVersion.SemanticVersion.Major}.{kubeVersion.SemanticVersion.Minor}"; + return version; + } + + private async Task FetchKindVersionsAsync() + { + console.WriteLine("Retrieving Kind Release Data..."); + var releases = await gitHub.Repository.Release.GetAll("kubernetes-sigs", "kind"); + var kindVersions = new KindVersionMap(); + foreach (var release in releases) + { + if (release.TagName.StartsWith("v")) + { + var version = release.TagName.TrimStart('v'); + var images = new List(); + // Try to extract image info from release body + if (!string.IsNullOrWhiteSpace(release.Body)) + { + images.AddRange(ParseImagesFromBody(release.Body)); + } + if (images.Count > 0) + { + kindVersions.Add(new KindVersion { Version = version, Images = images }); + } + } + } + console.WriteLine($"Retrieved {kindVersions.Count} Releases (Latest Release: {kindVersions.Max(x=>x.SemanticVersion)})"); + return kindVersions; + } + + public static List ParseImagesFromBody(string body) + { + var images = new Dictionary(); + var lines = body.Split('\n'); + var imageLineRegex = new System.Text.RegularExpressions.Regex(@"kindest/node:v(?[\d.]+)@sha256:(?[a-f0-9]+)"); + foreach (var rawLine in lines) + { + var line = rawLine.Trim(); + if (string.IsNullOrWhiteSpace(line)) continue; + var match = imageLineRegex.Match(line); + if (match.Success) + { + var version = match.Groups["version"].Value; + var sha = match.Groups["sha"].Value; + var imageString = $"kindest/node:v{version}@sha256:{sha}"; + // Deduplicate by version and image string + if (!images.ContainsKey(version)) + { + images[version] = new KubeImage + { + Version = version, + Image = imageString + }; + } + } + } + // Deduplicate by version and image string using LINQ + return images.Values + .GroupBy(img => new { img.Version, img.Image }) + .Select(g => g.First()) + .ToList(); + } +} \ No newline at end of file diff --git a/cli/src/Vdk/Services/ReverseProxyClient.cs b/cli/src/Vdk/Services/ReverseProxyClient.cs index 99c6604..747d36d 100644 --- a/cli/src/Vdk/Services/ReverseProxyClient.cs +++ b/cli/src/Vdk/Services/ReverseProxyClient.cs @@ -1,8 +1,11 @@ +using System.Runtime.CompilerServices; using k8s.Models; using KubeOps.KubernetesClient; using Vdk.Constants; using Vdk.Models; +[assembly: InternalsVisibleTo("Vdk.Tests")] + namespace Vdk.Services; internal class ReverseProxyClient : IReverseProxyClient @@ -10,17 +13,22 @@ internal class ReverseProxyClient : IReverseProxyClient private readonly IDockerEngine _docker; private readonly Func _client; private readonly IConsole _console; + - private static readonly string NginxConf = Path.Combine(".bin", "vega.conf"); - + private static readonly string NginxConf = Path.Combine("vega.conf"); + + private readonly IKindClient _kind; + + // ReverseProxyHostPort is 443 by default, unless REVERSE_PROXY_HOST_PORT is set as an env var private int ReverseProxyHostPort = GetEnvironmentVariableAsInt("REVERSE_PROXY_HOST_PORT", 443); - - public ReverseProxyClient(IDockerEngine docker, Func client, IConsole console) + + public ReverseProxyClient(IDockerEngine docker, Func client, IConsole console, IKindClient kind) { _docker = docker; _client = client; _console = console; + _kind = kind; } private const string StartComment = "##### START"; @@ -28,18 +36,9 @@ public ReverseProxyClient(IDockerEngine docker, Func public void Create() { - bool proxyExists = false; - try - { - proxyExists = _docker.Exists(Containers.ProxyName); - } - catch (Exception e) + var proxyExists = Exists(); + if (!proxyExists) { - _console.WriteWarning($"Failed to check if docker container {Containers.ProxyName} exists. Check configuration or try again."); - _console.WriteError(e); - return; - } - if (!proxyExists) { _console.WriteLine("Creating Vega VDK Proxy"); _console.WriteLine(" - This may take a few minutes..."); var conf = new FileInfo(NginxConf); @@ -49,33 +48,26 @@ public void Create() { conf.Directory.Create(); } - File.Create(conf.FullName).Dispose(); - using (var writer = conf.CreateText()) - { - writer.WriteLine("server {"); - writer.WriteLine($" listen {ReverseProxyHostPort} ssl;"); - writer.WriteLine($" listen [::]:{ReverseProxyHostPort} ssl;"); - writer.WriteLine(" http2 on;"); - writer.WriteLine(" server_name hub.dev-k8s.cloud;"); - writer.WriteLine($" ssl_certificate /etc/certs/fullchain.pem;"); - writer.WriteLine($" ssl_certificate_key /etc/certs/privkey.pem;"); - writer.WriteLine(" location / {"); - writer.WriteLine(" client_max_body_size 0;"); - writer.WriteLine(" proxy_pass http://host.docker.internal:5000;"); - writer.WriteLine(" proxy_set_header Host $host;"); - writer.WriteLine(" proxy_set_header X-Real-IP $remote_addr;"); - writer.WriteLine(" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;"); - writer.WriteLine(" proxy_set_header X-Forwarded-Proto $scheme;"); - writer.WriteLine(" }"); - writer.WriteLine("}"); - } + + InitConfFile(conf); } else { - _console.WriteWarning($" - Reverse proxy configuration for {conf.FullName} exists, skipping creation of file. Ensure it has the right values."); + _console.WriteLine($" - Reverse proxy configuration for {conf.FullName} exists, running a quick validation..."); + conf.Delete(); + InitConfFile(conf); + // iterate the clusters and create the endpoints for each + _kind.ListClusters().ForEach(tuple => + { + if (tuple is { isVdk: true, master: not null } && tuple.master.HttpsHostPort.HasValue) + { + _console.WriteLine($" - Adding cluster {tuple.name} to reverse proxy configuration"); + UpsertCluster(tuple.name, tuple.master.HttpsHostPort.Value, tuple.master.HttpHostPort.Value, false); + } + }); } - var fullChain = new FileInfo(Path.Combine(".bin", "Certs", "fullchain.pem")); - var privKey = new FileInfo(Path.Combine(".bin", "Certs", "privkey.pem")); + var fullChain = new FileInfo(Path.Combine("Certs", "fullchain.pem")); + var privKey = new FileInfo(Path.Combine("Certs", "privkey.pem")); try { _docker.Run(Containers.ProxyImage, Containers.ProxyName, @@ -88,7 +80,6 @@ public void Create() new FileMapping() { Destination = "/etc/certs/privkey.pem", Source = privKey.FullName }, }, null); - } catch (Exception e) { @@ -101,6 +92,46 @@ public void Create() } } + public bool Exists() + { + try + { + return _docker.Exists(Containers.ProxyName); + } + catch (Exception e) + { + _console.WriteWarning($"Failed to check if docker container {Containers.ProxyName} exists. Check configuration or try again."); + _console.WriteError(e); + return true; + } + + return false; + } + + private void InitConfFile(FileInfo conf) + { + File.Create(conf.FullName).Dispose(); + using (var writer = conf.CreateText()) + { + writer.WriteLine("server {"); + writer.WriteLine($" listen {ReverseProxyHostPort} ssl;"); + writer.WriteLine($" listen [::]:{ReverseProxyHostPort} ssl;"); + writer.WriteLine(" http2 on;"); + writer.WriteLine(" server_name hub.dev-k8s.cloud;"); + writer.WriteLine($" ssl_certificate /etc/certs/fullchain.pem;"); + writer.WriteLine($" ssl_certificate_key /etc/certs/privkey.pem;"); + writer.WriteLine(" location / {"); + writer.WriteLine(" client_max_body_size 0;"); + writer.WriteLine(" proxy_pass http://host.docker.internal:5000;"); + writer.WriteLine(" proxy_set_header Host $host;"); + writer.WriteLine(" proxy_set_header X-Real-IP $remote_addr;"); + writer.WriteLine(" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;"); + writer.WriteLine(" proxy_set_header X-Forwarded-Proto $scheme;"); + writer.WriteLine(" }"); + writer.WriteLine("}"); + } + } + public void Delete() { if (_docker.Exists(Containers.ProxyName, false)) @@ -115,7 +146,7 @@ public void Delete() } } - public void UpsertCluster(string clusterName, int targetPortHttps, int targetPortHttp) + public void UpsertCluster(string clusterName, int targetPortHttps, int targetPortHttp, bool reload = true) { // create a new server block in the nginx conf pointing to the target port listening on the https://clusterName.dev-k8s.cloud domain // reload the nginx configuration @@ -127,7 +158,7 @@ public void UpsertCluster(string clusterName, int targetPortHttps, int targetPor writer.WriteLine("server {"); writer.WriteLine($" listen {ReverseProxyHostPort} ssl;"); writer.WriteLine($" listen [::]:{ReverseProxyHostPort} ssl;"); - writer.WriteLine(" http2 on;"); + writer.WriteLine(" http2 on;"); writer.WriteLine($" server_name {clusterName}.dev-k8s.cloud;"); writer.WriteLine(" ssl_certificate /etc/certs/fullchain.pem;"); writer.WriteLine(" ssl_certificate_key /etc/certs/privkey.pem;"); @@ -148,41 +179,41 @@ public void UpsertCluster(string clusterName, int targetPortHttps, int targetPor _console.WriteWarning($"Error clearing cluster configuration ({NginxConf}): {e.Message}"); _console.WriteWarning("Please check the configuration and try again."); } - + // wait until the namespace vega exists before proceeding with the secrets creation bool nsVegaExists = false; int nTimesWaiting = 0; const int maxTimesWaiting = 60; while (!nsVegaExists && nTimesWaiting < maxTimesWaiting) { - if (_client(clusterName).Get("vega") == null) + if (_client(clusterName).Get("vega-system") == null) { nsVegaExists = false; if (nTimesWaiting % 5 == 0) - _console.WriteLine("Namespace 'vega' does not exist yet. Waiting..."); + _console.WriteLine("Namespace 'vega-system' does not exist yet. Waiting..."); Thread.Sleep(5000); nTimesWaiting++; } else { - _console.WriteLine("Namespace 'vega' already created by flux."); + _console.WriteLine("Namespace 'vega-system' already created by flux."); nsVegaExists = true; } } if (nTimesWaiting >= maxTimesWaiting) { - _console.WriteError("Namespace 'vega' does not exist after waiting. Please check the configuration and try again."); + _console.WriteError("Namespace 'vega-system' does not exist after waiting. Please check the configuration and try again."); return; } - + // write the cert secret to the cluster var tls = new V1Secret() { Metadata = new() { Name = "dev-tls", - NamespaceProperty = "vega" + NamespaceProperty = "vega-system" }, Type = "kubernetes.io/tls", Data = new Dictionary @@ -191,13 +222,15 @@ public void UpsertCluster(string clusterName, int targetPortHttps, int targetPor { "tls.key", File.ReadAllBytes("Certs/privkey.pem") } } }; - if (_client(clusterName).Get("dev-tls", "vega") == null) + var secret = _client(clusterName).Get("dev-tls", "vega-system"); + if (secret != null) { - _console.WriteLine("Creating vega secret"); - _client(clusterName).Create(tls); + _client(clusterName).Delete(secret); } - - ReloadConfigs(); + _console.WriteLine("Creating vega-system secret"); + _client(clusterName).Create(tls); + if (reload) + ReloadConfigs(); } private void ReloadConfigs() @@ -270,7 +303,7 @@ public void List() { throw new NotImplementedException(); } - + private static int GetEnvironmentVariableAsInt(string variableName, int defaultValue = 0) { string strValue = Environment.GetEnvironmentVariable(variableName); diff --git a/cli/src/Vdk/Vdk.csproj b/cli/src/Vdk/Vdk.csproj index 0ce07dc..2eb81bd 100644 --- a/cli/src/Vdk/Vdk.csproj +++ b/cli/src/Vdk/Vdk.csproj @@ -19,13 +19,15 @@ - - + + + - - + + + diff --git a/cli/tests/Vdk.Tests/Data/KindVersionData.json b/cli/tests/Vdk.Tests/Data/KindVersionData.json new file mode 100644 index 0000000..9beda6e --- /dev/null +++ b/cli/tests/Vdk.Tests/Data/KindVersionData.json @@ -0,0 +1,873 @@ +[ + { + "version": "0.27.0", + "images": [ + { + "version": "1.32.2", + "image": "kindest/node:v1.32.2@sha256:f226345927d7e348497136874b6d207e0b32cc52154ad8323129352923a3142f" + }, + { + "version": "1.31.6", + "image": "kindest/node:v1.31.6@sha256:28b7cbb993dfe093c76641a0c95807637213c9109b761f1d422c2400e22b8e87" + }, + { + "version": "1.30.10", + "image": "kindest/node:v1.30.10@sha256:4de75d0e82481ea846c0ed1de86328d821c1e6a6a91ac37bf804e5313670e507" + }, + { + "version": "1.29.14", + "image": "kindest/node:v1.29.14@sha256:8703bd94ee24e51b778d5556ae310c6c0fa67d761fae6379c8e0bb480e6fea29" + }, + { + "version": "1.33.0", + "image": "kindest/node:v1.33.0@sha256:02f73d6ae3f11ad5d543f16736a2cb2a63a300ad60e81dac22099b0b04784a4e" + } + ] + }, + { + "version": "0.26.0", + "images": [ + { + "version": "1.32.0", + "image": "kindest/node:v1.32.0@sha256:c48c62eac5da28cdadcf560d1d8616cfa6783b58f0d94cf63ad1bf49600cb027" + }, + { + "version": "1.31.4", + "image": "kindest/node:v1.31.4@sha256:2cb39f7295fe7eafee0842b1052a599a4fb0f8bcf3f83d96c7f4864c357c6c30" + }, + { + "version": "1.30.8", + "image": "kindest/node:v1.30.8@sha256:17cd608b3971338d9180b00776cb766c50d0a0b6b904ab4ff52fd3fc5c6369bf" + }, + { + "version": "1.29.12", + "image": "kindest/node:v1.29.12@sha256:62c0672ba99a4afd7396512848d6fc382906b8f33349ae68fb1dbfe549f70dec" + } + ] + }, + { + "version": "0.25.0", + "images": [ + { + "version": "1.31.2", + "image": "kindest/node:v1.31.2@sha256:18fbefc20a7113353c7b75b5c869d7145a6abd6269154825872dc59c1329912e" + }, + { + "version": "1.30.6", + "image": "kindest/node:v1.30.6@sha256:b6d08db72079ba5ae1f4a88a09025c0a904af3b52387643c285442afb05ab994" + }, + { + "version": "1.29.10", + "image": "kindest/node:v1.29.10@sha256:3b2d8c31753e6c8069d4fc4517264cd20e86fd36220671fb7d0a5855103aa84b" + }, + { + "version": "1.28.15", + "image": "kindest/node:v1.28.15@sha256:a7c05c7ae043a0b8c818f5a06188bc2c4098f6cb59ca7d1856df00375d839251" + }, + { + "version": "1.27.16", + "image": "kindest/node:v1.27.16@sha256:2d21a61643eafc439905e18705b8186f3296384750a835ad7a005dceb9546d20" + }, + { + "version": "1.26.15", + "image": "kindest/node:v1.26.15@sha256:c79602a44b4056d7e48dc20f7504350f1e87530fe953428b792def00bc1076dd" + }, + { + "version": "1.32.0", + "image": "kindest/node:v1.32.0@sha256:2458b423d635d7b01637cac2d6de7e1c1dca1148a2ba2e90975e214ca849e7cb" + } + ] + }, + { + "version": "0.24.0", + "images": [ + { + "version": "1.31.0", + "image": "kindest/node:v1.31.0@sha256:53df588e04085fd41ae12de0c3fe4c72f7013bba32a20e7325357a1ac94ba865" + }, + { + "version": "1.30.4", + "image": "kindest/node:v1.30.4@sha256:976ea815844d5fa93be213437e3ff5754cd599b040946b5cca43ca45c2047114" + }, + { + "version": "1.30.3", + "image": "kindest/node:v1.30.3@sha256:bf91e1ef2f7d92bb7734b2b896b3dddea98f0496b34d96e37dd5d7df331b7e56" + }, + { + "version": "1.29.8", + "image": "kindest/node:v1.29.8@sha256:d46b7aa29567e93b27f7531d258c372e829d7224b25e3fc6ffdefed12476d3aa" + }, + { + "version": "1.29.7", + "image": "kindest/node:v1.29.7@sha256:f70ab5d833fca132a100c1f95490be25d76188b053f49a3c0047ff8812360baf" + }, + { + "version": "1.28.13", + "image": "kindest/node:v1.28.13@sha256:45d319897776e11167e4698f6b14938eb4d52eb381d9e3d7a9086c16c69a8110" + }, + { + "version": "1.28.12", + "image": "kindest/node:v1.28.12@sha256:fa0e48b1e83bb8688a5724aa7eebffbd6337abd7909ad089a2700bf08c30c6ea" + }, + { + "version": "1.27.16", + "image": "kindest/node:v1.27.16@sha256:3fd82731af34efe19cd54ea5c25e882985bafa2c9baefe14f8deab1737d9fabe" + }, + { + "version": "1.26.15", + "image": "kindest/node:v1.26.15@sha256:1cc15d7b1edd2126ef051e359bf864f37bbcf1568e61be4d2ed1df7a3e87b354" + }, + { + "version": "1.25.16", + "image": "kindest/node:v1.25.16@sha256:6110314339b3b44d10da7d27881849a87e092124afab5956f2e10ecdb463b025" + } + ] + }, + { + "version": "0.23.0", + "images": [ + { + "version": "1.30.0", + "image": "kindest/node:v1.30.0@sha256:047357ac0cfea04663786a612ba1eaba9702bef25227a794b52890dd8bcd692e" + }, + { + "version": "1.29.4", + "image": "kindest/node:v1.29.4@sha256:3abb816a5b1061fb15c6e9e60856ec40d56b7b52bcea5f5f1350bc6e2320b6f8" + }, + { + "version": "1.28.9", + "image": "kindest/node:v1.28.9@sha256:dca54bc6a6079dd34699d53d7d4ffa2e853e46a20cd12d619a09207e35300bd0" + }, + { + "version": "1.27.13", + "image": "kindest/node:v1.27.13@sha256:17439fa5b32290e3ead39ead1250dca1d822d94a10d26f1981756cd51b24b9d8" + }, + { + "version": "1.26.15", + "image": "kindest/node:v1.26.15@sha256:84333e26cae1d70361bb7339efb568df1871419f2019c80f9a12b7e2d485fe19" + }, + { + "version": "1.25.16", + "image": "kindest/node:v1.25.16@sha256:5da57dfc290ac3599e775e63b8b6c49c0c85d3fec771cd7d55b45fae14b38d3b" + } + ] + }, + { + "version": "0.22.0", + "images": [ + { + "version": "1.29.2", + "image": "kindest/node:v1.29.2@sha256:51a1434a5397193442f0be2a297b488b6c919ce8a3931be0ce822606ea5ca245" + }, + { + "version": "1.29.1", + "image": "kindest/node:v1.29.1@sha256:0c06baa545c3bb3fbd4828eb49b8b805f6788e18ce67bff34706ffa91866558b" + }, + { + "version": "1.28.7", + "image": "kindest/node:v1.28.7@sha256:9bc6c451a289cf96ad0bbaf33d416901de6fd632415b076ab05f5fa7e4f65c58" + }, + { + "version": "1.28.6", + "image": "kindest/node:v1.28.6@sha256:e9e59d321795595d0eed0de48ef9fbda50388dc8bd4a9b23fb9bd869f370ec7e" + }, + { + "version": "1.27.11", + "image": "kindest/node:v1.27.11@sha256:681253009e68069b8e01aad36a1e0fa8cf18bb0ab3e5c4069b2e65cafdd70843" + }, + { + "version": "1.27.10", + "image": "kindest/node:v1.27.10@sha256:e6b2f72f22a4de7b957cd5541e519a8bef3bae7261dd30c6df34cd9bdd3f8476" + }, + { + "version": "1.26.14", + "image": "kindest/node:v1.26.14@sha256:5d548739ddef37b9318c70cb977f57bf3e5015e4552be4e27e57280a8cbb8e4f" + }, + { + "version": "1.26.13", + "image": "kindest/node:v1.26.13@sha256:8cb4239d64ff897e0c21ad19fe1d68c3422d4f3c1c1a734b7ab9ccc76c549605" + }, + { + "version": "1.25.16", + "image": "kindest/node:v1.25.16@sha256:e8b50f8e06b44bb65a93678a65a26248fae585b3d3c2a669e5ca6c90c69dc519" + }, + { + "version": "1.24.17", + "image": "kindest/node:v1.24.17@sha256:bad10f9b98d54586cba05a7eaa1b61c6b90bfc4ee174fdc43a7b75ca75c95e51" + }, + { + "version": "1.23.17", + "image": "kindest/node:v1.23.17@sha256:14d0a9a892b943866d7e6be119a06871291c517d279aedb816a4b4bc0ec0a5b3" + } + ] + }, + { + "version": "0.21.0", + "images": [ + { + "version": "1.29.1", + "image": "kindest/node:v1.29.1@sha256:a0cc28af37cf39b019e2b448c54d1a3f789de32536cb5a5db61a49623e527144" + }, + { + "version": "1.28.6", + "image": "kindest/node:v1.28.6@sha256:b7e1cf6b2b729f604133c667a6be8aab6f4dde5bb042c1891ae248d9154f665b" + }, + { + "version": "1.27.10", + "image": "kindest/node:v1.27.10@sha256:3700c811144e24a6c6181065265f69b9bf0b437c45741017182d7c82b908918f" + }, + { + "version": "1.26.13", + "image": "kindest/node:v1.26.13@sha256:15ae92d507b7d4aec6e8920d358fc63d3b980493db191d7327541fbaaed1f789" + }, + { + "version": "1.25.16", + "image": "kindest/node:v1.25.16@sha256:9d0a62b55d4fe1e262953be8d406689b947668626a357b5f9d0cfbddbebbc727" + }, + { + "version": "1.24.17", + "image": "kindest/node:v1.24.17@sha256:ea292d57ec5dd0e2f3f5a2d77efa246ac883c051ff80e887109fabefbd3125c7" + }, + { + "version": "1.23.17", + "image": "kindest/node:v1.23.17@sha256:fbb92ac580fce498473762419df27fa8664dbaa1c5a361b5957e123b4035bdcf" + } + ] + }, + { + "version": "0.20.0", + "images": [ + { + "version": "1.27.3", + "image": "kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72" + }, + { + "version": "1.26.6", + "image": "kindest/node:v1.26.6@sha256:6e2d8b28a5b601defe327b98bd1c2d1930b49e5d8c512e1895099e4504007adb" + }, + { + "version": "1.25.11", + "image": "kindest/node:v1.25.11@sha256:227fa11ce74ea76a0474eeefb84cb75d8dad1b08638371ecf0e86259b35be0c8" + }, + { + "version": "1.24.15", + "image": "kindest/node:v1.24.15@sha256:7db4f8bea3e14b82d12e044e25e34bd53754b7f2b0e9d56df21774e6f66a70ab" + }, + { + "version": "1.23.17", + "image": "kindest/node:v1.23.17@sha256:59c989ff8a517a93127d4a536e7014d28e235fb3529d9fba91b3951d461edfdb" + }, + { + "version": "1.22.17", + "image": "kindest/node:v1.22.17@sha256:f5b2e5698c6c9d6d0adc419c0deae21a425c07d81bbf3b6a6834042f25d4fba2" + }, + { + "version": "1.21.14", + "image": "kindest/node:v1.21.14@sha256:8a4e9bb3f415d2bb81629ce33ef9c76ba514c14d707f9797a01e3216376ba093" + }, + { + "version": "1.28.0", + "image": "kindest/node:v1.28.0@sha256:b7a4cad12c197af3ba43202d3efe03246b3f0793f162afb40a33c923952d5b31" + }, + { + "version": "1.29.0", + "image": "kindest/node:v1.29.0@sha256:eaa1450915475849a73a9227b8f201df25e55e268e5d619312131292e324d570" + } + ] + }, + { + "version": "0.19.0", + "images": [ + { + "version": "1.27.1", + "image": "kindest/node:v1.27.1@sha256:b7d12ed662b873bd8510879c1846e87c7e676a79fefc93e17b2a52989d3ff42b" + }, + { + "version": "1.26.4", + "image": "kindest/node:v1.26.4@sha256:f4c0d87be03d6bea69f5e5dc0adb678bb498a190ee5c38422bf751541cebe92e" + }, + { + "version": "1.25.9", + "image": "kindest/node:v1.25.9@sha256:c08d6c52820aa42e533b70bce0c2901183326d86dcdcbedecc9343681db45161" + }, + { + "version": "1.24.13", + "image": "kindest/node:v1.24.13@sha256:cea86276e698af043af20143f4bf0509e730ec34ed3b7fa790cc0bea091bc5dd" + }, + { + "version": "1.23.17", + "image": "kindest/node:v1.23.17@sha256:f77f8cf0b30430ca4128cc7cfafece0c274a118cd0cdb251049664ace0dee4ff" + }, + { + "version": "1.22.17", + "image": "kindest/node:v1.22.17@sha256:9af784f45a584f6b28bce2af84c494d947a05bd709151466489008f80a9ce9d5" + }, + { + "version": "1.21.14", + "image": "kindest/node:v1.21.14@sha256:220cfafdf6e3915fbce50e13d1655425558cb98872c53f802605aa2fb2d569cf" + }, + { + "version": "1.28.0", + "image": "kindest/node:v1.28.0@sha256:dad5a6238c5e41d7cac405fae3b5eda2ad1de6f1190fa8bfc64ff5bb86173213" + } + ] + }, + { + "version": "0.18.0", + "images": [ + { + "version": "1.26.3", + "image": "kindest/node:v1.26.3@sha256:61b92f38dff6ccc29969e7aa154d34e38b89443af1a2c14e6cfbd2df6419c66f" + }, + { + "version": "1.25.8", + "image": "kindest/node:v1.25.8@sha256:00d3f5314cc35327706776e95b2f8e504198ce59ac545d0200a89e69fce10b7f" + }, + { + "version": "1.24.12", + "image": "kindest/node:v1.24.12@sha256:1e12918b8bc3d4253bc08f640a231bb0d3b2c5a9b28aa3f2ca1aee93e1e8db16" + }, + { + "version": "1.23.17", + "image": "kindest/node:v1.23.17@sha256:e5fd1d9cd7a9a50939f9c005684df5a6d145e8d695e78463637b79464292e66c" + }, + { + "version": "1.22.17", + "image": "kindest/node:v1.22.17@sha256:c8a828709a53c25cbdc0790c8afe12f25538617c7be879083248981945c38693" + }, + { + "version": "1.21.14", + "image": "kindest/node:v1.21.14@sha256:27ef72ea623ee879a25fe6f9982690a3e370c68286f4356bf643467c552a3888" + }, + { + "version": "1.27.1", + "image": "kindest/node:v1.27.1@sha256:9915f5629ef4d29f35b478e819249e89cfaffcbfeebda4324e5c01d53d937b09" + }, + { + "version": "1.27.0", + "image": "kindest/node:v1.27.0@sha256:c6b22e613523b1af67d4bc8a0c38a4c3ea3a2b8fbc5b367ae36345c9cb844518" + } + ] + }, + { + "version": "0.17.0", + "images": [ + { + "version": "1.25.3", + "image": "kindest/node:v1.25.3@sha256:f52781bc0d7a19fb6c405c2af83abfeb311f130707a0e219175677e366cc45d1" + }, + { + "version": "1.24.7", + "image": "kindest/node:v1.24.7@sha256:577c630ce8e509131eab1aea12c022190978dd2f745aac5eb1fe65c0807eb315" + }, + { + "version": "1.23.13", + "image": "kindest/node:v1.23.13@sha256:ef453bb7c79f0e3caba88d2067d4196f427794086a7d0df8df4f019d5e336b61" + }, + { + "version": "1.22.15", + "image": "kindest/node:v1.22.15@sha256:7d9708c4b0873f0fe2e171e2b1b7f45ae89482617778c1c875f1053d4cef2e41" + }, + { + "version": "1.21.14", + "image": "kindest/node:v1.21.14@sha256:9d9eb5fb26b4fbc0c6d95fa8c790414f9750dd583f5d7cee45d92e8c26670aa1" + }, + { + "version": "1.20.15", + "image": "kindest/node:v1.20.15@sha256:a32bf55309294120616886b5338f95dd98a2f7231519c7dedcec32ba29699394" + }, + { + "version": "1.19.16", + "image": "kindest/node:v1.19.16@sha256:476cb3269232888437b61deca013832fee41f9f074f9bed79f57e4280f7c48b7" + }, + { + "version": "1.26.0", + "image": "kindest/node:v1.26.0@sha256:691e24bd2417609db7e589e1a479b902d2e209892a10ce375fab60a8407c7352" + } + ] + }, + { + "version": "0.16.0", + "images": [ + { + "version": "1.25.2", + "image": "kindest/node:v1.25.2@sha256:9be91e9e9cdf116809841fc77ebdb8845443c4c72fe5218f3ae9eb57fdb4bace" + }, + { + "version": "1.24.6", + "image": "kindest/node:v1.24.6@sha256:97e8d00bc37a7598a0b32d1fabd155a96355c49fa0d4d4790aab0f161bf31be1" + }, + { + "version": "1.23.12", + "image": "kindest/node:v1.23.12@sha256:9402cf1330bbd3a0d097d2033fa489b2abe40d479cc5ef47d0b6a6960613148a" + }, + { + "version": "1.22.15", + "image": "kindest/node:v1.22.15@sha256:bfd5eaae36849bfb3c1e3b9442f3da17d730718248939d9d547e86bbac5da586" + }, + { + "version": "1.21.14", + "image": "kindest/node:v1.21.14@sha256:ad5b7446dd8332439f22a1efdac73670f0da158c00f0a70b45716e7ef3fae20b" + }, + { + "version": "1.20.15", + "image": "kindest/node:v1.20.15@sha256:45d0194a8069c46483a0e509088ab9249302af561ebee76a1281a1f08ecb4ed3" + }, + { + "version": "1.19.16", + "image": "kindest/node:v1.19.16@sha256:a146f9819fece706b337d34125bbd5cb8ae4d25558427bf2fa3ee8ad231236f2" + } + ] + }, + { + "version": "0.15.0", + "images": [ + { + "version": "1.25.0", + "image": "kindest/node:v1.25.0@sha256:428aaa17ec82ccde0131cb2d1ca6547d13cf5fdabcc0bbecf749baa935387cbf" + }, + { + "version": "1.24.4", + "image": "kindest/node:v1.24.4@sha256:adfaebada924a26c2c9308edd53c6e33b3d4e453782c0063dc0028bdebaddf98" + }, + { + "version": "1.23.10", + "image": "kindest/node:v1.23.10@sha256:f047448af6a656fae7bc909e2fab360c18c487ef3edc93f06d78cdfd864b2d12" + }, + { + "version": "1.22.13", + "image": "kindest/node:v1.22.13@sha256:4904eda4d6e64b402169797805b8ec01f50133960ad6c19af45173a27eadf959" + }, + { + "version": "1.21.14", + "image": "kindest/node:v1.21.14@sha256:f9b4d3d1112f24a7254d2ee296f177f628f9b4c1b32f0006567af11b91c1f301" + }, + { + "version": "1.20.15", + "image": "kindest/node:v1.20.15@sha256:d67de8f84143adebe80a07672f370365ec7d23f93dc86866f0e29fa29ce026fe" + }, + { + "version": "1.19.16", + "image": "kindest/node:v1.19.16@sha256:707469aac7e6805e52c3bde2a8a8050ce2b15decff60db6c5077ba9975d28b98" + }, + { + "version": "1.18.20", + "image": "kindest/node:v1.18.20@sha256:61c9e1698c1cb19c3b1d8151a9135b379657aee23c59bde4a8d87923fcb43a91" + } + ] + }, + { + "version": "0.14.0", + "images": [ + { + "version": "1.24.0", + "image": "kindest/node:v1.24.0@sha256:0866296e693efe1fed79d5e6c7af8df71fc73ae45e3679af05342239cdc5bc8e" + }, + { + "version": "1.23.6", + "image": "kindest/node:v1.23.6@sha256:b1fa224cc6c7ff32455e0b1fd9cbfd3d3bc87ecaa8fcb06961ed1afb3db0f9ae" + }, + { + "version": "1.22.9", + "image": "kindest/node:v1.22.9@sha256:8135260b959dfe320206eb36b3aeda9cffcb262f4b44cda6b33f7bb73f453105" + }, + { + "version": "1.21.12", + "image": "kindest/node:v1.21.12@sha256:f316b33dd88f8196379f38feb80545ef3ed44d9197dca1bfd48bcb1583210207" + }, + { + "version": "1.20.15", + "image": "kindest/node:v1.20.15@sha256:6f2d011dffe182bad80b85f6c00e8ca9d86b5b8922cdf433d53575c4c5212248" + }, + { + "version": "1.19.16", + "image": "kindest/node:v1.19.16@sha256:d9c819e8668de8d5030708e484a9fdff44d95ec4675d136ef0a0a584e587f65c" + }, + { + "version": "1.18.20", + "image": "kindest/node:v1.18.20@sha256:738cdc23ed4be6cc0b7ea277a2ebcc454c8373d7d8fb991a7fcdbd126188e6d7" + } + ] + }, + { + "version": "0.13.0", + "images": [ + { + "version": "1.24.0", + "image": "kindest/node:v1.24.0@sha256:406fd86d48eaf4c04c7280cd1d2ca1d61e7d0d61ddef0125cb097bc7b82ed6a1" + }, + { + "version": "1.23.6", + "image": "kindest/node:v1.23.6@sha256:1af0f1bee4c3c0fe9b07de5e5d3fafeb2eec7b4e1b268ae89fcab96ec67e8355" + }, + { + "version": "1.22.9", + "image": "kindest/node:v1.22.9@sha256:6e57a6b0c493c7d7183a1151acff0bfa44bf37eb668826bf00da5637c55b6d5e" + }, + { + "version": "1.21.12", + "image": "kindest/node:v1.21.12@sha256:ae05d44cc636ee961068399ea5123ae421790f472c309900c151a44ee35c3e3e" + }, + { + "version": "1.20.15", + "image": "kindest/node:v1.20.15@sha256:a6ce604504db064c5e25921c6c0fffea64507109a1f2a512b1b562ac37d652f3" + }, + { + "version": "1.19.16", + "image": "kindest/node:v1.19.16@sha256:dec41184d10deca01a08ea548197b77dc99eeacb56ff3e371af3193c86ca99f4" + }, + { + "version": "1.18.20", + "image": "kindest/node:v1.18.20@sha256:38a8726ece5d7867fb0ede63d718d27ce2d41af519ce68be5ae7fcca563537ed" + } + ] + }, + { + "version": "0.12.0", + "images": [ + { + "version": "1.23.4", + "image": "kindest/node:v1.23.4@sha256:0e34f0d0fd448aa2f2819cfd74e99fe5793a6e4938b328f657c8e3f81ee0dfb9" + }, + { + "version": "1.22.7", + "image": "kindest/node:v1.22.7@sha256:1dfd72d193bf7da64765fd2f2898f78663b9ba366c2aa74be1fd7498a1873166" + }, + { + "version": "1.21.10", + "image": "kindest/node:v1.21.10@sha256:84709f09756ba4f863769bdcabe5edafc2ada72d3c8c44d6515fc581b66b029c" + }, + { + "version": "1.20.15", + "image": "kindest/node:v1.20.15@sha256:393bb9096c6c4d723bb17bceb0896407d7db581532d11ea2839c80b28e5d8deb" + }, + { + "version": "1.19.16", + "image": "kindest/node:v1.19.16@sha256:81f552397c1e6c1f293f967ecb1344d8857613fb978f963c30e907c32f598467" + }, + { + "version": "1.18.20", + "image": "kindest/node:v1.18.20@sha256:e3dca5e16116d11363e31639640042a9b1bd2c90f85717a7fc66be34089a8169" + }, + { + "version": "1.17.17", + "image": "kindest/node:v1.17.17@sha256:e477ee64df5731aa4ef4deabbafc34e8d9a686b49178f726563598344a3898d5" + }, + { + "version": "1.16.15", + "image": "kindest/node:v1.16.15@sha256:64bac16b83b6adfd04ea3fbcf6c9b5b893277120f2b2cbf9f5fa3e5d4c2260cc" + }, + { + "version": "1.15.12", + "image": "kindest/node:v1.15.12@sha256:9dfc13db6d3fd5e5b275f8c4657ee6a62ef9cb405546664f2de2eabcfd6db778" + }, + { + "version": "1.14.10", + "image": "kindest/node:v1.14.10@sha256:b693339da2a927949025869425e20daf80111ccabf020d4021a23c00bae29d82" + } + ] + }, + { + "version": "0.11.1", + "images": [ + { + "version": "1.21.1", + "image": "kindest/node:v1.21.1@sha256:fae9a58f17f18f06aeac9772ca8b5ac680ebbed985e266f711d936e91d113bad" + }, + { + "version": "1.20.7", + "image": "kindest/node:v1.20.7@sha256:cbeaf907fc78ac97ce7b625e4bf0de16e3ea725daf6b04f930bd14c67c671ff9" + }, + { + "version": "1.19.11", + "image": "kindest/node:v1.19.11@sha256:07db187ae84b4b7de440a73886f008cf903fcf5764ba8106a9fd5243d6f32729" + }, + { + "version": "1.18.19", + "image": "kindest/node:v1.18.19@sha256:7af1492e19b3192a79f606e43c35fb741e520d195f96399284515f077b3b622c" + }, + { + "version": "1.17.17", + "image": "kindest/node:v1.17.17@sha256:66f1d0d91a88b8a001811e2f1054af60eef3b669a9a74f9b6db871f2f1eeed00" + }, + { + "version": "1.16.15", + "image": "kindest/node:v1.16.15@sha256:83067ed51bf2a3395b24687094e283a7c7c865ccc12a8b1d7aa673ba0c5e8861" + }, + { + "version": "1.15.12", + "image": "kindest/node:v1.15.12@sha256:b920920e1eda689d9936dfcf7332701e80be12566999152626b2c9d730397a95" + }, + { + "version": "1.14.10", + "image": "kindest/node:v1.14.10@sha256:f8a66ef82822ab4f7569e91a5bccaf27bceee135c1457c512e54de8c6f7219f8" + }, + { + "version": "1.22.0", + "image": "kindest/node:v1.22.0@sha256:b8bda84bb3a190e6e028b1760d277454a72267a5454b57db34437c34a588d047" + }, + { + "version": "1.23.0", + "image": "kindest/node:v1.23.0@sha256:49824ab1727c04e56a21a5d8372a402fcd32ea51ac96a2706a12af38934f81ac" + } + ] + }, + { + "version": "0.11.0", + "images": [ + { + "version": "1.21.1", + "image": "kindest/node:v1.21.1@sha256:fae9a58f17f18f06aeac9772ca8b5ac680ebbed985e266f711d936e91d113bad" + }, + { + "version": "1.20.7", + "image": "kindest/node:v1.20.7@sha256:e645428988191fc824529fd0bb5c94244c12401cf5f5ea3bd875eb0a787f0fe9" + }, + { + "version": "1.19.11", + "image": "kindest/node:v1.19.11@sha256:7664f21f9cb6ba2264437de0eb3fe99f201db7a3ac72329547ec4373ba5f5911" + }, + { + "version": "1.18.19", + "image": "kindest/node:v1.18.19@sha256:530378628c7c518503ade70b1df698b5de5585dcdba4f349328d986b8849b1ee" + }, + { + "version": "1.17.17", + "image": "kindest/node:v1.17.17@sha256:c581fbf67f720f70aaabc74b44c2332cc753df262b6c0bca5d26338492470c17" + }, + { + "version": "1.16.15", + "image": "kindest/node:v1.16.15@sha256:430c03034cd856c1f1415d3e37faf35a3ea9c5aaa2812117b79e6903d1fc9651" + }, + { + "version": "1.15.12", + "image": "kindest/node:v1.15.12@sha256:8d575f056493c7778935dd855ded0e95c48cb2fab90825792e8fc9af61536bf9" + }, + { + "version": "1.14.10", + "image": "kindest/node:v1.14.10@sha256:6033e04bcfca7c5f2a9c4ce77551e1abf385bcd2709932ec2f6a9c8c0aff6d4f" + } + ] + }, + { + "version": "0.10.0", + "images": [ + { + "version": "1.20.2", + "image": "kindest/node:v1.20.2@sha256:8f7ea6e7642c0da54f04a7ee10431549c0257315b3a634f6ef2fecaaedb19bab" + }, + { + "version": "1.19.7", + "image": "kindest/node:v1.19.7@sha256:a70639454e97a4b733f9d9b67e12c01f6b0297449d5b9cbbef87473458e26dca" + }, + { + "version": "1.18.15", + "image": "kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4" + }, + { + "version": "1.17.17", + "image": "kindest/node:v1.17.17@sha256:7b6369d27eee99c7a85c48ffd60e11412dc3f373658bc59b7f4d530b7056823e" + }, + { + "version": "1.16.15", + "image": "kindest/node:v1.16.15@sha256:c10a63a5bda231c0a379bf91aebf8ad3c79146daca59db816fb963f731852a99" + }, + { + "version": "1.15.12", + "image": "kindest/node:v1.15.12@sha256:67181f94f0b3072fb56509107b380e38c55e23bf60e6f052fbd8052d26052fb5" + }, + { + "version": "1.14.10", + "image": "kindest/node:v1.14.10@sha256:3fbed72bcac108055e46e7b4091eb6858ad628ec51bf693c21f5ec34578f6180" + } + ] + }, + { + "version": "0.9.0", + "images": [ + { + "version": "1.19.1", + "image": "kindest/node:v1.19.1@sha256:98cf5288864662e37115e362b23e4369c8c4a408f99cbc06e58ac30ddc721600" + }, + { + "version": "1.18.8", + "image": "kindest/node:v1.18.8@sha256:f4bcc97a0ad6e7abaf3f643d890add7efe6ee4ab90baeb374b4f41a4c95567eb" + }, + { + "version": "1.17.11", + "image": "kindest/node:v1.17.11@sha256:5240a7a2c34bf241afb54ac05669f8a46661912eab05705d660971eeb12f6555" + }, + { + "version": "1.16.15", + "image": "kindest/node:v1.16.15@sha256:a89c771f7de234e6547d43695c7ab047809ffc71a0c3b65aa54eda051c45ed20" + }, + { + "version": "1.15.12", + "image": "kindest/node:v1.15.12@sha256:d9b939055c1e852fe3d86955ee24976cab46cba518abcb8b13ba70917e6547a6" + }, + { + "version": "1.14.10", + "image": "kindest/node:v1.14.10@sha256:ce4355398a704fca68006f8a29f37aafb49f8fc2f64ede3ccd0d9198da910146" + }, + { + "version": "1.13.12", + "image": "kindest/node:v1.13.12@sha256:1c1a48c2bfcbae4d5f4fa4310b5ed10756facad0b7a2ca93c7a4b5bae5db29f5" + } + ] + }, + { + "version": "0.8.0", + "images": [ + { + "version": "1.18.2", + "image": "kindest/node:v1.18.2@sha256:7b27a6d0f2517ff88ba444025beae41491b016bc6af573ba467b70c5e8e0d85f" + }, + { + "version": "1.17.5", + "image": "kindest/node:v1.17.5@sha256:ab3f9e6ec5ad8840eeb1f76c89bb7948c77bbf76bcebe1a8b59790b8ae9a283a" + }, + { + "version": "1.16.9", + "image": "kindest/node:v1.16.9@sha256:7175872357bc85847ec4b1aba46ed1d12fa054c83ac7a8a11f5c268957fd5765" + }, + { + "version": "1.15.11", + "image": "kindest/node:v1.15.11@sha256:6cc31f3533deb138792db2c7d1ffc36f7456a06f1db5556ad3b6927641016f50" + }, + { + "version": "1.14.10", + "image": "kindest/node:v1.14.10@sha256:6cd43ff41ae9f02bb46c8f455d5323819aec858b99534a290517ebc181b443c6" + }, + { + "version": "1.13.12", + "image": "kindest/node:v1.13.12@sha256:214476f1514e47fe3f6f54d0f9e24cfb1e4cda449529791286c7161b7f9c08e7" + }, + { + "version": "1.12.10", + "image": "kindest/node:v1.12.10@sha256:faeb82453af2f9373447bb63f50bae02b8020968e0889c7fa308e19b348916cb" + }, + { + "version": "1.11.10", + "image": "kindest/node:v1.11.10@sha256:74c8740710649a3abb169e7f348312deff88fc97d74cfb874c5095ab3866bb42" + } + ] + }, + { + "version": "0.7.0", + "images": [ + { + "version": "1.17.0", + "image": "kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62" + }, + { + "version": "1.16.4", + "image": "kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55" + } + ] + }, + { + "version": "0.6.1", + "images": [ + { + "version": "1.16.3", + "image": "kindest/node:v1.16.3@sha256:70ce6ce09bee5c34ab14aec2b84d6edb260473a60638b1b095470a3a0f95ebec" + }, + { + "version": "1.15.6", + "image": "kindest/node:v1.15.6@sha256:18c4ab6b61c991c249d29df778e651f443ac4bcd4e6bdd37e0c83c0d33eaae78" + }, + { + "version": "1.14.9", + "image": "kindest/node:v1.14.9@sha256:bdd3731588fa3ce8f66c7c22f25351362428964b6bca13048659f68b9e665b72" + }, + { + "version": "1.13.12", + "image": "kindest/node:v1.13.12@sha256:1fe072c080ee129a2a440956a65925ab3bbd1227cf154e2ade145b8e59a584ad" + }, + { + "version": "1.12.10", + "image": "kindest/node:v1.12.10@sha256:c5aeca1433e3230e6c1a96b5e1cd79c90139fd80242189b370a3248a05d77118" + }, + { + "version": "1.11.10", + "image": "kindest/node:v1.11.10@sha256:8ebe805201da0a988ee9bbcc2de2ac0031f9264ac24cf2a598774f1e7b324fe1" + } + ] + }, + { + "version": "0.6.0", + "images": [ + { + "version": "1.16.3", + "image": "kindest/node:v1.16.3@sha256:bced4bc71380b59873ea3917afe9fb35b00e174d22f50c7cab9188eac2b0fb88" + } + ] + }, + { + "version": "0.5.0", + "images": [ + { + "version": "1.15.3", + "image": "kindest/node:v1.15.3@sha256:27e388752544890482a86b90d8ac50fcfa63a2e8656a96ec5337b902ec8e5157" + }, + { + "version": "1.14.6", + "image": "kindest/node:v1.14.6@sha256:464a43f5cf6ad442f100b0ca881a3acae37af069d5f96849c1d06ced2870888d" + }, + { + "version": "1.13.10", + "image": "kindest/node:v1.13.10@sha256:2f5f882a6d0527a2284d29042f3a6a07402e1699d792d0d5a9b9a48ef155fa2a" + }, + { + "version": "1.12.10", + "image": "kindest/node:v1.12.10@sha256:e43003c6714cc5a9ba7cf1137df3a3b52ada5c3f2c77f8c94a4d73c82b64f6f3" + }, + { + "version": "1.11.10", + "image": "kindest/node:v1.11.10@sha256:bb22258625199ba5e47fb17a8a8a7601e536cd03456b42c1ee32672302b1f909" + } + ] + }, + { + "version": "0.4.0", + "images": [ + { + "version": "1.15.0", + "image": "kindest/node:v1.15.0@sha256:b4d092fd2b507843dd096fe6c85d06a27a0cbd740a0b32a880fe61aba24bb478" + }, + { + "version": "1.14.3", + "image": "kindest/node:v1.14.3@sha256:583166c121482848cd6509fbac525dd62d503c52a84ff45c338ee7e8b5cfe114" + }, + { + "version": "1.13.7", + "image": "kindest/node:v1.13.7@sha256:f3f1cfc2318d1eb88d91253a9c5fa45f6e9121b6b1e65aea6c7ef59f1549aaaf" + }, + { + "version": "1.12.9", + "image": "kindest/node:v1.12.9@sha256:bcb79eb3cd6550c1ba9584ce57c832dcd6e442913678d2785307a7ad9addc029" + }, + { + "version": "1.11.10", + "image": "kindest/node:v1.11.10@sha256:176845d919899daef63d0dbd1cf62f79902c38b8d2a86e5fa041e491ab795d33" + } + ] + }, + { + "version": "0.3.0", + "images": [ + { + "version": "1.13.6", + "image": "kindest/node:v1.13.6@sha256:9e07014fb48c746deb98ec8aafd58c3918622eca6063e643c6e6d86c86e170b4" + }, + { + "version": "1.12.8", + "image": "kindest/node:v1.12.8@sha256:cc6e1a928a85c14b52e32ea97a198393fb68097f14c4d4c454a8a3bc1d8d486c" + }, + { + "version": "1.11.10", + "image": "kindest/node:v1.11.10@sha256:abd0275ead5ddfd477b7bc491f71957d7dd75408a346834ffe8a9bee5fbdc15b" + } + ] + } +] \ No newline at end of file diff --git a/cli/tests/Vdk.Tests/Models/KindVersionMapTests.cs b/cli/tests/Vdk.Tests/Models/KindVersionMapTests.cs index 9a3d959..460bd6a 100644 --- a/cli/tests/Vdk.Tests/Models/KindVersionMapTests.cs +++ b/cli/tests/Vdk.Tests/Models/KindVersionMapTests.cs @@ -94,11 +94,11 @@ public class KindVersionMapTests [InlineData("0.12.0", "1.16", "kindest/node:v1.16.15@sha256:64bac16b83b6adfd04ea3fbcf6c9b5b893277120f2b2cbf9f5fa3e5d4c2260cc")] [InlineData("0.12.0", "1.15", "kindest/node:v1.15.12@sha256:9dfc13db6d3fd5e5b275f8c4657ee6a62ef9cb405546664f2de2eabcfd6db778")] [InlineData("0.12.0", "1.14", "kindest/node:v1.14.10@sha256:b693339da2a927949025869425e20daf80111ccabf020d4021a23c00bae29d82")] - [InlineData("0.11.1", "1.21", "kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6")] + [InlineData("0.11.1", "1.21", "kindest/node:v1.21.1@sha256:fae9a58f17f18f06aeac9772ca8b5ac680ebbed985e266f711d936e91d113bad")] [InlineData("0.11.1", "1.20", "kindest/node:v1.20.7@sha256:cbeaf907fc78ac97ce7b625e4bf0de16e3ea725daf6b04f930bd14c67c671ff9")] [InlineData("0.11.1", "1.19", "kindest/node:v1.19.11@sha256:07db187ae84b4b7de440a73886f008cf903fcf5764ba8106a9fd5243d6f32729")] - [InlineData("0.11.1", "1.18", "kindest/node:v1.18.19@sha256:7af1492e19b3192a79f606e43c35fb741e520d195f96399284515f077b3b622c ")] - [InlineData("0.11.1", "1.17", "kindest/node:v1.17.17@sha256:66f1d0d91a88b8a001811e2f1054af60eef3b669a9a74f9b6db871f2f1eeed00 ")] + [InlineData("0.11.1", "1.18", "kindest/node:v1.18.19@sha256:7af1492e19b3192a79f606e43c35fb741e520d195f96399284515f077b3b622c")] + [InlineData("0.11.1", "1.17", "kindest/node:v1.17.17@sha256:66f1d0d91a88b8a001811e2f1054af60eef3b669a9a74f9b6db871f2f1eeed00")] [InlineData("0.11.1", "1.16", "kindest/node:v1.16.15@sha256:83067ed51bf2a3395b24687094e283a7c7c865ccc12a8b1d7aa673ba0c5e8861")] [InlineData("0.11.1", "1.15", "kindest/node:v1.15.12@sha256:b920920e1eda689d9936dfcf7332701e80be12566999152626b2c9d730397a95")] [InlineData("0.11.1", "1.14", "kindest/node:v1.14.10@sha256:f8a66ef82822ab4f7569e91a5bccaf27bceee135c1457c512e54de8c6f7219f8")] @@ -107,11 +107,11 @@ public class KindVersionMapTests [InlineData("0.11.0", "1.21", "kindest/node:v1.21.1@sha256:fae9a58f17f18f06aeac9772ca8b5ac680ebbed985e266f711d936e91d113bad")] [InlineData("0.11.0", "1.20", "kindest/node:v1.20.7@sha256:e645428988191fc824529fd0bb5c94244c12401cf5f5ea3bd875eb0a787f0fe9")] [InlineData("0.11.0", "1.19", "kindest/node:v1.19.11@sha256:7664f21f9cb6ba2264437de0eb3fe99f201db7a3ac72329547ec4373ba5f5911")] - [InlineData("0.11.0", "1.18", "kindest/node:v1.18.19@sha256:530378628c7c518503ade70b1df698b5de5585dcdba4f349328d986b8849b1ee ")] + [InlineData("0.11.0", "1.18", "kindest/node:v1.18.19@sha256:530378628c7c518503ade70b1df698b5de5585dcdba4f349328d986b8849b1ee")] [InlineData("0.11.0", "1.17", "kindest/node:v1.17.17@sha256:c581fbf67f720f70aaabc74b44c2332cc753df262b6c0bca5d26338492470c17")] [InlineData("0.11.0", "1.16", "kindest/node:v1.16.15@sha256:430c03034cd856c1f1415d3e37faf35a3ea9c5aaa2812117b79e6903d1fc9651")] - [InlineData("0.11.0", "1.15", "kindest/node:v1.15.12@sha256:8d575f056493c7778935dd855ded0e95c48cb2fab90825792e8fc9af61536bf9 ")] - [InlineData("0.11.0", "1.14", "kindest/node:v1.14.10@sha256:6033e04bcfca7c5f2a9c4ce77551e1abf385bcd2709932ec2f6a9c8c0aff6d4f ")] + [InlineData("0.11.0", "1.15", "kindest/node:v1.15.12@sha256:8d575f056493c7778935dd855ded0e95c48cb2fab90825792e8fc9af61536bf9")] + [InlineData("0.11.0", "1.14", "kindest/node:v1.14.10@sha256:6033e04bcfca7c5f2a9c4ce77551e1abf385bcd2709932ec2f6a9c8c0aff6d4f")] [InlineData("0.10.0", "1.20", "kindest/node:v1.20.2@sha256:8f7ea6e7642c0da54f04a7ee10431549c0257315b3a634f6ef2fecaaedb19bab")] [InlineData("0.10.0", "1.19", "kindest/node:v1.19.7@sha256:a70639454e97a4b733f9d9b67e12c01f6b0297449d5b9cbbef87473458e26dca")] [InlineData("0.10.0", "1.18", "kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4")] @@ -136,8 +136,8 @@ public class KindVersionMapTests [InlineData("0.8.0", "1.11", "kindest/node:v1.11.10@sha256:74c8740710649a3abb169e7f348312deff88fc97d74cfb874c5095ab3866bb42")] public void FindImage_WhenImageExistsForSpecifiedVersions_ShouldReturnExpectedImage(string kindVersion, string kubeVersion, string expected) { - var reader = new EmbeddedDataReader(new JsonObjectSerializer()); - var map = reader.ReadJsonObjects("Vdk.Data.KindVersionData.json"); + var reader = new EmbeddedDataReader(new JsonObjectSerializer(), typeof(KindVersionMapTests)); + var map = reader.ReadJsonObjects("Vdk.Tests.Data.KindVersionData.json"); var result = map.FindImage(kindVersion, kubeVersion); diff --git a/cli/tests/Vdk.Tests/ReverseProxyClientTests.cs b/cli/tests/Vdk.Tests/ReverseProxyClientTests.cs new file mode 100644 index 0000000..42e4144 --- /dev/null +++ b/cli/tests/Vdk.Tests/ReverseProxyClientTests.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Collections.Generic; +using FluentAssertions; +using Moq; +using Vdk.Services; +using Xunit; +using k8s.Models; +using KubeOps.KubernetesClient; + +namespace Vdk.Tests +{ + public class ReverseProxyClientTests + { + private readonly Mock _dockerMock = new(); + private readonly Mock _consoleMock = new(); + private readonly Mock _kindMock = new(); + private readonly Mock _k8sMock = new(); + private readonly Func _clientFunc; + + public ReverseProxyClientTests() + { + _clientFunc = _ => _k8sMock.Object; + } + + [Fact] + public void Constructor_SetsDependencies() + { + var client = new ReverseProxyClient(_dockerMock.Object, _clientFunc, _consoleMock.Object, _kindMock.Object); + client.Should().NotBeNull(); + } + + [Fact] + public void Exists_ReturnsTrue_WhenDockerThrows() + { + _dockerMock.Setup(d => d.Exists(It.IsAny(), It.IsAny())).Throws(new Exception("fail")); + var client = new ReverseProxyClient(_dockerMock.Object, _clientFunc, _consoleMock.Object, _kindMock.Object); + client.Exists().Should().BeTrue(); + } + + [Fact] + public void Exists_ReturnsFalse_WhenDockerReturnsFalse() + { + _dockerMock.Setup(d => d.Exists(It.IsAny(), It.IsAny())).Returns(false); + var client = new ReverseProxyClient(_dockerMock.Object, _clientFunc, _consoleMock.Object, _kindMock.Object); + client.Exists().Should().BeFalse(); + } + + [Fact] + public void Exists_ReturnsTrue_WhenDockerReturnsTrue() + { + _dockerMock.Setup(d => d.Exists(It.IsAny(), It.IsAny())).Returns(true); + var client = new ReverseProxyClient(_dockerMock.Object, _clientFunc, _consoleMock.Object, _kindMock.Object); + client.Exists().Should().BeTrue(); + } + + [Fact] + public void InitConfFile_CreatesFileAndWritesConfig() + { + var tempFile = Path.GetTempFileName(); + var file = new FileInfo(tempFile); + var client = new ReverseProxyClient(_dockerMock.Object, _clientFunc, _consoleMock.Object, _kindMock.Object); + client.GetType().GetMethod("InitConfFile", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + .Invoke(client, new object[] { file }); + File.Exists(tempFile).Should().BeTrue(); + File.ReadAllText(tempFile).Should().Contain("server {"); + File.Delete(tempFile); + } + } +} \ No newline at end of file diff --git a/cli/tests/Vdk.Tests/Vdk.Tests.csproj b/cli/tests/Vdk.Tests/Vdk.Tests.csproj index 370275a..6a032ff 100644 --- a/cli/tests/Vdk.Tests/Vdk.Tests.csproj +++ b/cli/tests/Vdk.Tests/Vdk.Tests.csproj @@ -9,6 +9,14 @@ true + + + + + + + + diff --git a/devbox.json b/devbox.json index 40c5943..9db97d9 100644 --- a/devbox.json +++ b/devbox.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.12.0/.schema/devbox.schema.json", "packages": [ - "kind@0.23.0", + "kind@latest", "fluxcd@latest", "docker@latest", "jq@latest" diff --git a/devbox.lock b/devbox.lock index 4362cd8..44ad225 100644 --- a/devbox.lock +++ b/devbox.lock @@ -2,104 +2,108 @@ "lockfile_version": "1", "packages": { "docker@latest": { - "last_modified": "2024-11-05T01:08:39Z", - "resolved": "github:NixOS/nixpkgs/a04d33c0c3f1a59a2c1cb0c6e34cd24500e5a1dc#docker", + "last_modified": "2025-05-06T08:06:31Z", + "resolved": "github:NixOS/nixpkgs/1cb1c02a6b1b7cf67e3d7731cbbf327a53da9679#docker", "source": "devbox-search", - "version": "27.3.1", + "version": "27.5.1", "systems": { "aarch64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/n2l6y7zp830kasbw0xirfhqliniln54l-docker-27.3.1", + "path": "/nix/store/gpa7wchjd0pnbjfj8x8vz3vjd3badjmi-docker-27.5.1", "default": true } ], - "store_path": "/nix/store/n2l6y7zp830kasbw0xirfhqliniln54l-docker-27.3.1" + "store_path": "/nix/store/gpa7wchjd0pnbjfj8x8vz3vjd3badjmi-docker-27.5.1" }, "aarch64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/30w5k9rjzsjhscahps94d0bhd7f57pv8-docker-27.3.1", + "path": "/nix/store/adysdn0jbh1lki910w4xawv01f96c4nz-docker-27.5.1", "default": true } ], - "store_path": "/nix/store/30w5k9rjzsjhscahps94d0bhd7f57pv8-docker-27.3.1" + "store_path": "/nix/store/adysdn0jbh1lki910w4xawv01f96c4nz-docker-27.5.1" }, "x86_64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/l4cfzw1bvvcqn0s1yyvc2pxmjz17mymv-docker-27.3.1", + "path": "/nix/store/3k5gpbimvafhgbv6a1h41jcdbb3lkc04-docker-27.5.1", "default": true } ], - "store_path": "/nix/store/l4cfzw1bvvcqn0s1yyvc2pxmjz17mymv-docker-27.3.1" + "store_path": "/nix/store/3k5gpbimvafhgbv6a1h41jcdbb3lkc04-docker-27.5.1" }, "x86_64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/267rdap7pn4wg03q2akrm5lx9xsls6rk-docker-27.3.1", + "path": "/nix/store/m9gwvzfwhabv1f6fkhv6cci4a1xhins4-docker-27.5.1", "default": true } ], - "store_path": "/nix/store/267rdap7pn4wg03q2akrm5lx9xsls6rk-docker-27.3.1" + "store_path": "/nix/store/m9gwvzfwhabv1f6fkhv6cci4a1xhins4-docker-27.5.1" } } }, "fluxcd@latest": { - "last_modified": "2024-07-31T08:48:38Z", - "resolved": "github:NixOS/nixpkgs/c3392ad349a5227f4a3464dce87bcc5046692fce#fluxcd", + "last_modified": "2025-05-06T08:06:31Z", + "resolved": "github:NixOS/nixpkgs/1cb1c02a6b1b7cf67e3d7731cbbf327a53da9679#fluxcd", "source": "devbox-search", - "version": "2.3.0", + "version": "2.5.1", "systems": { "aarch64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/p93yvwa3ji8ch9mrf0nl4p7mi101m0sa-fluxcd-2.3.0", + "path": "/nix/store/yf792k56vrf4mdrpa675qkbxxypyanc4-fluxcd-2.5.1", "default": true } ], - "store_path": "/nix/store/p93yvwa3ji8ch9mrf0nl4p7mi101m0sa-fluxcd-2.3.0" + "store_path": "/nix/store/yf792k56vrf4mdrpa675qkbxxypyanc4-fluxcd-2.5.1" }, "aarch64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/hsg8gwv96hri1hyi35iyvymyl7qx5y0y-fluxcd-2.3.0", + "path": "/nix/store/00a8x6s33ficnsvpyvji867s7slgrxxf-fluxcd-2.5.1", "default": true } ], - "store_path": "/nix/store/hsg8gwv96hri1hyi35iyvymyl7qx5y0y-fluxcd-2.3.0" + "store_path": "/nix/store/00a8x6s33ficnsvpyvji867s7slgrxxf-fluxcd-2.5.1" }, "x86_64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/8rpkf79jhwc8f3wxyjd31ah6ipcfwhlk-fluxcd-2.3.0", + "path": "/nix/store/lk03p1mgw9r3krfzqmbmyn1yckb15m32-fluxcd-2.5.1", "default": true } ], - "store_path": "/nix/store/8rpkf79jhwc8f3wxyjd31ah6ipcfwhlk-fluxcd-2.3.0" + "store_path": "/nix/store/lk03p1mgw9r3krfzqmbmyn1yckb15m32-fluxcd-2.5.1" }, "x86_64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/0pz6yk9176ikpmynwpsn7iazqzjdzr56-fluxcd-2.3.0", + "path": "/nix/store/ldffdykly7rpwcds693qhqi0npx2lqar-fluxcd-2.5.1", "default": true } ], - "store_path": "/nix/store/0pz6yk9176ikpmynwpsn7iazqzjdzr56-fluxcd-2.3.0" + "store_path": "/nix/store/ldffdykly7rpwcds693qhqi0npx2lqar-fluxcd-2.5.1" } } }, + "github:NixOS/nixpkgs/nixpkgs-unstable": { + "last_modified": "2025-05-07T00:09:58Z", + "resolved": "github:NixOS/nixpkgs/b3582c75c7f21ce0b429898980eddbbf05c68e55?lastModified=1746576598&narHash=sha256-FshoQvr6Aor5SnORVvh%2FZdJ1Sa2U4ZrIMwKBX5k2wu0%3D" + }, "jq@latest": { - "last_modified": "2024-11-18T00:41:09Z", - "resolved": "github:NixOS/nixpkgs/5083ec887760adfe12af64830a66807423a859a7#jq", + "last_modified": "2025-05-06T16:34:47Z", + "resolved": "github:NixOS/nixpkgs/f90d0af5db814e870b2ad1aebc4e8924a30c53dd#jq", "source": "devbox-search", "version": "1.7.1", "systems": { @@ -107,157 +111,157 @@ "outputs": [ { "name": "bin", - "path": "/nix/store/1ayrnq8zpljx4m3kvgfs6kqv9vrm6zik-jq-1.7.1-bin", + "path": "/nix/store/d0r880glv6pvpxibssrcl2v7v4sqijfx-jq-1.7.1-bin", "default": true }, { "name": "man", - "path": "/nix/store/fy9h4wlb4a8r34jrqif25n2i24vaw9x2-jq-1.7.1-man", + "path": "/nix/store/51dbhjrpi3dnj3jv9npjk1wvqcjfpz2n-jq-1.7.1-man", "default": true }, { - "name": "dev", - "path": "/nix/store/05c8mk6qnrxgyazs2acg3816h57lvsvb-jq-1.7.1-dev" + "name": "out", + "path": "/nix/store/kd68ckkv7lqnfb46rk1746mhig0hhpl5-jq-1.7.1" }, { - "name": "doc", - "path": "/nix/store/zm4966xz8nyp9nzkn6ghhzh8hcw87bmv-jq-1.7.1-doc" + "name": "dev", + "path": "/nix/store/d3w37gvgsapsh612vyy0b0lnjq81bhhd-jq-1.7.1-dev" }, { - "name": "out", - "path": "/nix/store/1ldq0nn6gjpxqjxihzdd7vlppmxvmpxn-jq-1.7.1" + "name": "doc", + "path": "/nix/store/31mydlnw005lpaslc5yqgmxy5mfhzs98-jq-1.7.1-doc" } ], - "store_path": "/nix/store/1ayrnq8zpljx4m3kvgfs6kqv9vrm6zik-jq-1.7.1-bin" + "store_path": "/nix/store/d0r880glv6pvpxibssrcl2v7v4sqijfx-jq-1.7.1-bin" }, "aarch64-linux": { "outputs": [ { "name": "bin", - "path": "/nix/store/pixiqvakc6rxc10qfpy2w4dsgnmsdayv-jq-1.7.1-bin", + "path": "/nix/store/gn5585z1600aqspd8hyz48qjxpkprmmd-jq-1.7.1-bin", "default": true }, { "name": "man", - "path": "/nix/store/fgjncriv2sc52b8lwm5kzabkq26kirvk-jq-1.7.1-man", + "path": "/nix/store/333c44nih6riadc384l4kyrcsiqn9w6b-jq-1.7.1-man", "default": true }, { "name": "dev", - "path": "/nix/store/gn12vqlhg68r5gwsacsiksqm6255nl3s-jq-1.7.1-dev" + "path": "/nix/store/djgzdhs0aj2g37xfbbx5inmm5j34ri6s-jq-1.7.1-dev" }, { "name": "doc", - "path": "/nix/store/v8cvxadadrrvi8as046wiyzb96xvih19-jq-1.7.1-doc" + "path": "/nix/store/5iv52bfgs3dgg9sgy63m0v8kqf3jka61-jq-1.7.1-doc" }, { "name": "out", - "path": "/nix/store/5xm3x3a9jaq9fy4jrg44rk7b6hl9sgbv-jq-1.7.1" + "path": "/nix/store/qqb575m59p2l1v3h9hb3q96mm7b41qjf-jq-1.7.1" } ], - "store_path": "/nix/store/pixiqvakc6rxc10qfpy2w4dsgnmsdayv-jq-1.7.1-bin" + "store_path": "/nix/store/gn5585z1600aqspd8hyz48qjxpkprmmd-jq-1.7.1-bin" }, "x86_64-darwin": { "outputs": [ { "name": "bin", - "path": "/nix/store/g6m0ggpijkm26ygd8vvhyd07sgriz26j-jq-1.7.1-bin", + "path": "/nix/store/d7scikakdayw3xjw8pm55q8j146qwxwd-jq-1.7.1-bin", "default": true }, { "name": "man", - "path": "/nix/store/gr5mmlwr3y511brhq7nihbyixjhcn80k-jq-1.7.1-man", + "path": "/nix/store/giabrd1iawzw666w62n4dw7czs6k580i-jq-1.7.1-man", "default": true }, - { - "name": "out", - "path": "/nix/store/ganc2d577ffr1h3qcvxln1d5gn6z3m83-jq-1.7.1" - }, { "name": "dev", - "path": "/nix/store/cybp745j7fvdnxkf9889wiscn9cgbwjp-jq-1.7.1-dev" + "path": "/nix/store/h80zvna5sb5klg3q56l22rw2cac35pzm-jq-1.7.1-dev" }, { "name": "doc", - "path": "/nix/store/0yhbxjsrbh7g6x7ngfc1qg6q613qbqhp-jq-1.7.1-doc" + "path": "/nix/store/v51p982wn5hrjvfprr5h5f7ffakj86iq-jq-1.7.1-doc" + }, + { + "name": "out", + "path": "/nix/store/8fd0arnj59yvp8clz9a14p61dhchp8kk-jq-1.7.1" } ], - "store_path": "/nix/store/g6m0ggpijkm26ygd8vvhyd07sgriz26j-jq-1.7.1-bin" + "store_path": "/nix/store/d7scikakdayw3xjw8pm55q8j146qwxwd-jq-1.7.1-bin" }, "x86_64-linux": { "outputs": [ { "name": "bin", - "path": "/nix/store/75q9nlm0zn7cg6w151cc4mkqqk0mwaxa-jq-1.7.1-bin", + "path": "/nix/store/2yi7k5ay6xj73rbfqf170gms922rjm2d-jq-1.7.1-bin", "default": true }, { "name": "man", - "path": "/nix/store/3wwkjcpxyrryfb1dbxjp82kj8g8jaxzw-jq-1.7.1-man", + "path": "/nix/store/6y5xhrbdfwnf4xbwzvvz8n2yibyvkvm9-jq-1.7.1-man", "default": true }, { "name": "dev", - "path": "/nix/store/f5y04jklz034w0y55bnqhcfnd271spwa-jq-1.7.1-dev" + "path": "/nix/store/2186473hfwk15m3cmpddck6jp28sjmkl-jq-1.7.1-dev" }, { "name": "doc", - "path": "/nix/store/j0i0bzx90l10l3vxdwj29xjji1avp15a-jq-1.7.1-doc" + "path": "/nix/store/r6qdnj7qxcwvkp02zw5yjp09vydb0m8l-jq-1.7.1-doc" }, { "name": "out", - "path": "/nix/store/cpzlrkcbp2s87aya4hfxigj6a68m6yzz-jq-1.7.1" + "path": "/nix/store/d7mhlprb84m83vm0z1xw18jpxz3x5hzy-jq-1.7.1" } ], - "store_path": "/nix/store/75q9nlm0zn7cg6w151cc4mkqqk0mwaxa-jq-1.7.1-bin" + "store_path": "/nix/store/2yi7k5ay6xj73rbfqf170gms922rjm2d-jq-1.7.1-bin" } } }, - "kind@0.23.0": { - "last_modified": "2024-07-31T08:48:38Z", - "resolved": "github:NixOS/nixpkgs/c3392ad349a5227f4a3464dce87bcc5046692fce#kind", + "kind@latest": { + "last_modified": "2025-05-06T08:06:31Z", + "resolved": "github:NixOS/nixpkgs/1cb1c02a6b1b7cf67e3d7731cbbf327a53da9679#kind", "source": "devbox-search", - "version": "0.23.0", + "version": "0.27.0", "systems": { "aarch64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/kr4ninq2zzpcsa6zhsjhmf51s07akwf1-kind-0.23.0", + "path": "/nix/store/czsrh125agfs757l14d17p6a790rmjp4-kind-0.27.0", "default": true } ], - "store_path": "/nix/store/kr4ninq2zzpcsa6zhsjhmf51s07akwf1-kind-0.23.0" + "store_path": "/nix/store/czsrh125agfs757l14d17p6a790rmjp4-kind-0.27.0" }, "aarch64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/l8w7cnsf0yw7rp3gxry3v41x0by6p2vn-kind-0.23.0", + "path": "/nix/store/m46irqsy3faiy43m02wxphsn0p4q5cq9-kind-0.27.0", "default": true } ], - "store_path": "/nix/store/l8w7cnsf0yw7rp3gxry3v41x0by6p2vn-kind-0.23.0" + "store_path": "/nix/store/m46irqsy3faiy43m02wxphsn0p4q5cq9-kind-0.27.0" }, "x86_64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/4ifhijdnd34mzl5laq19racpny4056a0-kind-0.23.0", + "path": "/nix/store/q03i6bd2mrl8jaq3k7maiq8543937ngc-kind-0.27.0", "default": true } ], - "store_path": "/nix/store/4ifhijdnd34mzl5laq19racpny4056a0-kind-0.23.0" + "store_path": "/nix/store/q03i6bd2mrl8jaq3k7maiq8543937ngc-kind-0.27.0" }, "x86_64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/py3aak1f2mr4h8widpjyz7hzxbc48n0n-kind-0.23.0", + "path": "/nix/store/720xq11bmaxpi91qk1cg0r2s18k3rjik-kind-0.27.0", "default": true } ], - "store_path": "/nix/store/py3aak1f2mr4h8widpjyz7hzxbc48n0n-kind-0.23.0" + "store_path": "/nix/store/720xq11bmaxpi91qk1cg0r2s18k3rjik-kind-0.27.0" } } }