Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ jobs:
- name: Build the solution
run: dotnet build ./cli --no-restore --configuration Release
- name: Run tests
run: dotnet test ./cli --no-build --configuration Release --verbosity normal
run: VDK_TEST_MODE=1 dotnet test ./cli --no-build --configuration Release --verbosity normal
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ x86/
bld/
[Bb]in/
[Oo]bj/

# Allow code coverage output

[Ll]og/
[Ll]ogs/
[Pp]ackages
Expand Down Expand Up @@ -487,3 +490,6 @@ $RECYCLE.BIN/

# Intellij
*.iml

!coverage.json
!coverage.xml
12 changes: 12 additions & 0 deletions cli/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-coverage": {
"version": "17.14.2",
"commands": [
"dotnet-coverage"
]
}
}
}
8,561 changes: 8,561 additions & 0 deletions cli/output.cobertura.xml

Large diffs are not rendered by default.

26 changes: 23 additions & 3 deletions cli/src/Vdk/Commands/CreateClusterCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
using Vdk.Data;
using Vdk.Models;
using Vdk.Services;
using k8s.Models;
using System.Diagnostics;
using KubeOps.KubernetesClient;
using IConsole = Vdk.Services.IConsole;

namespace Vdk.Commands;
Expand All @@ -17,8 +20,9 @@
private readonly IKindClient _kind;
private readonly IFluxClient _flux;
private readonly IReverseProxyClient _reverseProxy;
private readonly Func<string, IKubernetesClient> _kubeClient;

public CreateClusterCommand(IConsole console, IEmbeddedDataReader dataReader, IYamlObjectSerializer yaml, IFileSystem fileSystem, IKindClient kind, IFluxClient flux, IReverseProxyClient reverseProxy)
public CreateClusterCommand(IConsole console, IEmbeddedDataReader dataReader, IYamlObjectSerializer yaml, IFileSystem fileSystem, IKindClient kind, IFluxClient flux, IReverseProxyClient reverseProxy, Func<string, IKubernetesClient> kubeClient)
: base("cluster", "Create a Vega development cluster")
{
_console = console;
Expand All @@ -28,6 +32,7 @@
_kind = kind;
_flux = flux;
_reverseProxy = reverseProxy;
_kubeClient = kubeClient;
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 @@ -36,11 +41,24 @@
AddOption(controlNodes);
AddOption(workers);
AddOption(kubeVersion);
this.SetHandler(InvokeAsync, nameOption, controlNodes, workers, kubeVersion);
this.SetHandler((string name, int controlPlaneNodes, int workerNodes, string kubeVersion, bool bypassPrompt) => InvokeAsync(name, controlPlaneNodes, workerNodes, kubeVersion, bypassPrompt),
nameOption, controlNodes, workers, kubeVersion, new Option<bool>("--bypassPrompt", () => false, "Bypass the tenant prompt (for tests)"));
}

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 kubeVersion = Defaults.KubeApiVersion, bool bypassPrompt = false)
{
// Ensure config exists and prompt if not
var config = ConfigManager.EnsureConfig(
promptTenantId: () => {
_console.Write("Tenant GUID: ");
return Console.ReadLine();
},
openBrowser: () => {
var url = "https://archetypical.software/register";
try { Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }); } catch { }
},
bypassPrompt: bypassPrompt);

var map = _dataReader.ReadJsonObjects<KindVersionMap>("Vdk.Data.KindVersionData.json");
string? kindVersion = null;
try
Expand Down Expand Up @@ -136,15 +154,17 @@
}

_flux.Bootstrap(name.ToLower(), "./clusters/default", branch: "main");


try
{
_reverseProxy.UpsertCluster(name.ToLower(), masterNode.ExtraPortMappings.First().HostPort,

Check warning on line 161 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);
}
catch (Exception e)
{
// print the stack trace
_console.WriteLine(e.StackTrace);

Check warning on line 167 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;
}
Expand Down
11 changes: 9 additions & 2 deletions cli/src/Vdk/Commands/CreateRegistryCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using Vdk.Services;
using IConsole = Vdk.Services.IConsole;

Expand All @@ -18,7 +18,14 @@ public CreateRegistryCommand(IConsole console, IHubClient client): base("registr

public Task InvokeAsync()
{
_client.Create();
try
{
_client.Create();
}
catch (Exception ex)
{
_console.WriteError("Error creating registry: {0}", ex.Message);
}
return Task.CompletedTask;
}
}
19 changes: 16 additions & 3 deletions cli/src/Vdk/Commands/InitializeCommand.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.CommandLine;
using System.CommandLine;
using Vdk.Constants;
using Vdk.Services;
using System.Diagnostics;
using IConsole = Vdk.Services.IConsole;

namespace Vdk.Commands;
Expand All @@ -21,11 +22,23 @@ public InitializeCommand(CreateClusterCommand createCluster, CreateProxyCommand
_createRegistry = createRegistry;
_kind = kind;
_console = console;
this.SetHandler(InvokeAsync);
this.SetHandler((bool bypassPrompt) => InvokeAsync(bypassPrompt), new Option<bool>("--bypassPrompt", () => false, "Bypass the tenant prompt (for tests)"));
}

public async Task InvokeAsync()
public async Task InvokeAsync(bool bypassPrompt = false)
{
// Ensure config exists and prompt if not
var config = ConfigManager.EnsureConfig(
promptTenantId: () => {
_console.Write("Tenant GUID: ");
return Console.ReadLine();
},
openBrowser: () => {
var url = "https://archetypical.software/register";
try { Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }); } catch { }
},
bypassPrompt: bypassPrompt);

var name = Defaults.ClusterName;
var controlPlaneNodes = 1;
var workerNodes = 2;
Expand Down
11 changes: 9 additions & 2 deletions cli/src/Vdk/Commands/RemoveProxyCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using Vdk.Services;
using IConsole = Vdk.Services.IConsole;

Expand All @@ -18,7 +18,14 @@ public RemoveProxyCommand(IConsole console, IReverseProxyClient client) : base("

public Task InvokeAsync()
{
_client.Delete();
try
{
_client.Delete();
}
catch (Exception ex)
{
_console.WriteError("Error removing proxy: {0}", ex.Message);
}
return Task.CompletedTask;
}
}
11 changes: 9 additions & 2 deletions cli/src/Vdk/Commands/RemoveRegistryCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using Vdk.Services;
using IConsole = Vdk.Services.IConsole;

Expand All @@ -18,7 +18,14 @@ public RemoveRegistryCommand(IConsole console, IHubClient client) : base("regist

public Task InvokeAsync()
{
_client.Destroy();
try
{
_client.Destroy();
}
catch (Exception ex)
{
_console.WriteError("Error removing registry: {0}", ex.Message);
}
return Task.CompletedTask;
}
}
78 changes: 78 additions & 0 deletions cli/src/Vdk/Services/ConfigManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text.Json;

namespace Vdk.Services
{
public class VdkConfig
{
public string TenantId { get; set; } = string.Empty;
public string Env { get; set; } = "VDK";
}

public static class ConfigManager
{
private static readonly string ConfigFileName = ".vdkconfig.json";
private static string ConfigPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigFileName);

public static VdkConfig? LoadConfig()
{
if (!File.Exists(ConfigPath)) return null;
try
{
var json = File.ReadAllText(ConfigPath);
return JsonSerializer.Deserialize<VdkConfig>(json);
}
catch
{
return null;
}
}

public static void SaveConfig(VdkConfig config)
{
var json = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(ConfigPath, json);
}

public static VdkConfig EnsureConfig(Func<string?> promptTenantId, Action openBrowser, bool bypassPrompt = false)
{
if (bypassPrompt)
{
return new VdkConfig { TenantId = "test-tenant-id", Env = "Test" };
}
// Bypass tenant prompt for unit tests
var testMode = Environment.GetEnvironmentVariable("VDK_TEST_MODE");
if (!string.IsNullOrEmpty(testMode) && testMode.Equals("1"))
{
return new VdkConfig { TenantId = "test-tenant-id", Env = "Test" };
}
var config = LoadConfig();
if (config != null && !string.IsNullOrWhiteSpace(config.TenantId))
return config;

while (true)
{
// (Normal prompt logic continues here)

Console.WriteLine("Enter your Tenant GUID (or leave blank to create an account):");
var input = promptTenantId();
if (string.IsNullOrWhiteSpace(input))
{
Console.WriteLine("Opening browser to create an account...");
openBrowser();
continue;
}
// Validate GUID
if (Guid.TryParse(input, out _))
{
config = new VdkConfig { TenantId = input.Trim() };
SaveConfig(config);
return config;
}
Console.WriteLine("Invalid GUID. Please try again.");
}
}
}
}
25 changes: 25 additions & 0 deletions cli/src/Vdk/Services/KubernetesConfigMapHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using k8s.Models;

namespace Vdk.Services
{
public static class KubernetesConfigMapHelper
{
public static V1ConfigMap CreateClusterInfoConfigMap(string tenantId, string env)
{
return new V1ConfigMap
{
Metadata = new V1ObjectMeta
{
Name = "cluster-info",
NamespaceProperty = "vega-system"
},
Data = new Dictionary<string, string>
{
{ "TenantId", tenantId },
{ "Env", env }
}
};
}
}
}
Loading
Loading