2
0
mirror of synced 2025-02-22 13:08:08 +00:00

uniform config parsing

This commit is contained in:
benbierens 2023-06-26 13:58:41 +02:00
parent 125c3715a8
commit 868553f27d
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
12 changed files with 8920 additions and 174 deletions

221
ArgsUniform/ArgsUniform.cs Normal file
View File

@ -0,0 +1,221 @@
using System.Reflection;
namespace ArgsUniform
{
public class ArgsUniform<T>
{
private readonly object? defaultsProvider;
private readonly IEnv.IEnv env;
private readonly string[] args;
private const int cliStart = 8;
private const int shortStart = 38;
private const int envStart = 48;
private const int descStart = 80;
public ArgsUniform(params string[] args)
: this(null!, args)
{
}
public ArgsUniform(object defaultsProvider, params string[] args)
: this(defaultsProvider, new IEnv.Env(), args)
{
}
public ArgsUniform(IEnv.IEnv env, params string[] args)
: this(null!, env, args)
{
}
public ArgsUniform(object defaultsProvider, IEnv.IEnv env, params string[] args)
{
this.defaultsProvider = defaultsProvider;
this.env = env;
this.args = args;
}
public T Parse(bool printResult = false)
{
var result = Activator.CreateInstance<T>();
var uniformProperties = typeof(T).GetProperties().Where(m => m.GetCustomAttributes(typeof(UniformAttribute), false).Length == 1).ToArray();
var missingRequired = new List<PropertyInfo>();
foreach (var uniformProperty in uniformProperties)
{
var attr = uniformProperty.GetCustomAttribute<UniformAttribute>();
if (attr != null)
{
if (!UniformAssign(result, attr, uniformProperty) && attr.Required)
{
missingRequired.Add(uniformProperty);
}
}
}
if (missingRequired.Any())
{
Print("");
foreach (var missing in missingRequired)
{
var attr = missing.GetCustomAttribute<UniformAttribute>()!;
var exampleArg = $"--{attr.Arg}=...";
var exampleEnvVar = $"{attr.EnvVar}=...";
Print($" ! Missing required input. Use argument: '{exampleArg}' or environment variable: '{exampleEnvVar}'.");
}
PrintHelp();
throw new ArgumentException("Unable to assemble all required arguments");
}
if (printResult)
{
Print("");
foreach (var p in uniformProperties)
{
Print($"\t{p.Name} = {p.GetValue(result)}");
}
Print("");
}
return result;
}
public void PrintHelp()
{
Print("");
PrintAligned("CLI option:", "(short)", "Environment variable:", "Description");
var attrs = typeof(T).GetProperties().Where(m => m.GetCustomAttributes(typeof(UniformAttribute), false).Length == 1).Select(p => p.GetCustomAttribute<UniformAttribute>()).Where(a => a != null).ToArray();
foreach (var attr in attrs)
{
var a = attr!;
var optional = !a.Required ? " *" : "";
PrintAligned($"--{a.Arg}=...", $"({a.ArgShort})", a.EnvVar, a.Description + optional);
}
Print("");
}
private void Print(string msg)
{
Console.WriteLine(msg);
}
private void PrintAligned(string cli, string s, string env, string desc)
{
Console.CursorLeft = cliStart;
Console.Write(cli);
Console.CursorLeft = shortStart;
Console.Write(s);
Console.CursorLeft = envStart;
Console.Write(env);
Console.CursorLeft = descStart;
Console.Write(desc + Environment.NewLine);
}
private bool UniformAssign(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
if (AssignFromArgsIfAble(result, attr, uniformProperty)) return true;
if (AssignFromEnvVarIfAble(result, attr, uniformProperty)) return true;
if (AssignFromDefaultsIfAble(result, uniformProperty)) return true;
return false;
}
private bool AssignFromDefaultsIfAble(T result, PropertyInfo uniformProperty)
{
if (defaultsProvider == null) return true;
var defaultProperty = defaultsProvider.GetType().GetProperties().SingleOrDefault(p => p.Name == uniformProperty.Name);
if (defaultProperty == null) return true;
var value = defaultProperty.GetValue(defaultsProvider);
if (value != null)
{
Assign(result, uniformProperty, value);
return true;
}
return false;
}
private bool AssignFromEnvVarIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
var e = env.GetEnvVarOrDefault(attr.EnvVar, string.Empty);
if (!string.IsNullOrEmpty(e))
{
Assign(result, uniformProperty, e);
return true;
}
return false;
}
private bool AssignFromArgsIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
var fromArg = GetFromArgs(attr.Arg);
if (fromArg != null)
{
Assign(result, uniformProperty, fromArg);
return true;
}
var fromShort = GetFromArgs(attr.ArgShort);
if (fromShort != null)
{
Assign(result, uniformProperty, fromShort);
return true;
}
return false;
}
private bool Assign(T result, PropertyInfo uniformProperty, object value)
{
if (uniformProperty.PropertyType == value.GetType())
{
uniformProperty.SetValue(result, value);
return true;
}
else
{
if (uniformProperty.PropertyType == typeof(string) || uniformProperty.PropertyType == typeof(int))
{
uniformProperty.SetValue(result, Convert.ChangeType(value, uniformProperty.PropertyType));
return true;
}
else
{
if (uniformProperty.PropertyType == typeof(int?)) return AssignOptionalInt(result, uniformProperty, value);
if (uniformProperty.PropertyType.IsEnum) return AssignEnum(result, uniformProperty, value);
throw new NotSupportedException();
}
}
}
private static bool AssignEnum(T result, PropertyInfo uniformProperty, object value)
{
var s = value.ToString();
if (Enum.TryParse(uniformProperty.PropertyType, s, out var e))
{
uniformProperty.SetValue(result, e);
return true;
}
return false;
}
private static bool AssignOptionalInt(T result, PropertyInfo uniformProperty, object value)
{
if (int.TryParse(value.ToString(), out int i))
{
uniformProperty.SetValue(result, i);
return true;
}
return false;
}
private string? GetFromArgs(string key)
{
var argKey = $"--{key}=";
var arg = args.FirstOrDefault(a => a.StartsWith(argKey));
if (arg != null)
{
return arg.Substring(argKey.Length);
}
return null;
}
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="I-Env" Version="1.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,29 @@
namespace ArgsUniform
{
public class ExampleUser
{
public class Args
{
[Uniform("aaa", "a", "AAA", false, "Sets the AAA!")]
public string Aaa { get; set; } = string.Empty;
[Uniform("bbb", "b", "BBB", true, "Sets that BBB")]
public string Bbb { get; set; } = string.Empty;
}
public class DefaultsProvider
{
public string Aaa { get { return "non-static operation"; } }
}
public void Example()
{
// env var: "AAA=BBB"
var args = "--ccc=ddd";
var uniform = new ArgsUniform<Args>(new DefaultsProvider(), args);
var aaa = uniform.Parse();
}
}
}

View File

@ -0,0 +1,20 @@
namespace ArgsUniform
{
public class UniformAttribute : Attribute
{
public UniformAttribute(string arg, string argShort, string envVar, bool required, string description)
{
Arg = arg;
ArgShort = argShort;
EnvVar = envVar;
Required = required;
Description = description;
}
public string Arg { get; }
public string ArgShort { get; }
public string EnvVar { get; }
public bool Required { get; }
public string Description { get; }
}
}

View File

@ -1,93 +0,0 @@
namespace CodexNetDeployer
{
public class ArgOrVar
{
public static readonly ArgVar CodexImage = new ArgVar("codex-image", "CODEXIMAGE", "Docker image of Codex.");
public static readonly ArgVar GethImage = new ArgVar("geth-image", "GETHIMAGE", "Docker image of Geth.");
public static readonly ArgVar ContractsImage = new ArgVar("contracts-image", "CONTRACTSIMAGE", "Docker image of Codex Contracts deployer.");
public static readonly ArgVar KubeConfigFile = new ArgVar("kube-config", "KUBECONFIG", "Path to Kubeconfig file.");
public static readonly ArgVar KubeNamespace = new ArgVar("kube-namespace", "KUBENAMESPACE", "Kubernetes namespace to be used for deployment.");
public static readonly ArgVar NumberOfCodexNodes = new ArgVar("nodes", "NODES", "Number of Codex nodes to be created.");
public static readonly ArgVar NumberOfValidatorNodes = new ArgVar("validators", "VALIDATORS", "Number of Codex nodes that will be validating.");
public static readonly ArgVar StorageQuota = new ArgVar("storage-quota", "STORAGEQUOTA", "Storage quota in megabytes used by each Codex node.");
public static readonly ArgVar LogLevel = new ArgVar("log-level", "LOGLEVEL", "Log level used by each Codex node. [Trace, Debug*, Info, Warn, Error]");
private readonly string[] args;
public ArgOrVar(string[] args)
{
this.args = args;
}
public string Get(ArgVar key, string defaultValue = "")
{
var argKey = $"--{key.Arg}=";
var arg = args.FirstOrDefault(a => a.StartsWith(argKey));
if (arg != null)
{
return arg.Substring(argKey.Length);
}
var env = Environment.GetEnvironmentVariable(key.Var);
if (env != null)
{
return env;
}
return defaultValue;
}
public int? GetInt(ArgVar key)
{
var str = Get(key);
if (string.IsNullOrEmpty(str)) return null;
if (int.TryParse(str, out int result))
{
return result;
}
return null;
}
public void PrintHelp()
{
var nl = Environment.NewLine;
Console.WriteLine("CodexNetDeployer allows you to easily deploy multiple Codex nodes in a Kubernetes cluster. " +
"The deployer will set up the required supporting services, deploy the Codex on-chain contracts, start and bootstrap the Codex instances. " +
"All Kubernetes objects will be created in the namespace provided, allowing you to easily find, modify, and delete them afterwards." + nl);
Console.WriteLine("CodexNetDeployer assumes you are running this tool from *inside* the Kubernetes cluster you want to deploy to. " +
"If you are not running this from a container inside the cluster, add the argument '--external'." + nl);
Console.Write("\t[ CLI argument ] or [ Environment variable ]");
Console.CursorLeft = 70;
Console.Write("(Description)" + nl);
var fields = GetType().GetFields();
foreach (var field in fields)
{
var value = (ArgVar)field.GetValue(null)!;
value.PrintHelp();
}
}
}
public class ArgVar
{
public ArgVar(string arg, string var, string description)
{
Arg = arg;
Var = var;
Description = description;
}
public string Arg { get; }
public string Var { get; }
public string Description { get; }
public void PrintHelp()
{
Console.Write($"\t[ --{Arg}=... ] or [ {Var}=... ]");
Console.CursorLeft = 70;
Console.Write(Description + Environment.NewLine);
}
}
}

View File

@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ArgsUniform\ArgsUniform.csproj" />
<ProjectReference Include="..\DistTestCore\DistTestCore.csproj" />
</ItemGroup>

View File

@ -1,48 +1,47 @@
using DistTestCore;
using ArgsUniform;
using DistTestCore;
using DistTestCore.Codex;
using DistTestCore.Marketplace;
namespace CodexNetDeployer
{
public class Configuration
{
public Configuration(
string codexImage,
string gethImage,
string contractsImage,
string kubeConfigFile,
string kubeNamespace,
int? numberOfCodexNodes,
int? numberOfValidators,
int? storageQuota,
CodexLogLevel codexLogLevel,
TestRunnerLocation runnerLocation)
{
CodexImage = codexImage;
GethImage = gethImage;
ContractsImage = contractsImage;
KubeConfigFile = kubeConfigFile;
KubeNamespace = kubeNamespace;
NumberOfCodexNodes = numberOfCodexNodes;
NumberOfValidators = numberOfValidators;
StorageQuota = storageQuota;
CodexLogLevel = codexLogLevel;
RunnerLocation = runnerLocation;
}
[Uniform("codex-image", "ci", "CODEXIMAGE", true, "Docker image of Codex.")]
public string CodexImage { get; set; } = string.Empty;
public string CodexImage { get; }
public string GethImage { get; }
public string ContractsImage { get; }
public string KubeConfigFile { get; }
public string KubeNamespace { get; }
public int? NumberOfCodexNodes { get; }
public int? NumberOfValidators { get; }
public int? StorageQuota { get; }
public CodexLogLevel CodexLogLevel { get; }
public TestRunnerLocation RunnerLocation { get; }
[Uniform("geth-image", "gi", "GETHIMAGE", true, "Docker image of Geth.")]
public string GethImage { get; set; } = string.Empty;
public void PrintConfig()
[Uniform("contracts-image", "oi", "CONTRACTSIMAGE", true, "Docker image of Codex Contracts.")]
public string ContractsImage { get; set; } = string.Empty;
[Uniform("kube-config", "kc", "KUBECONFIG", true, "Path to Kubeconfig file.")]
public string KubeConfigFile { get; set; } = string.Empty;
[Uniform("kube-namespace", "kn", "KUBENAMESPACE", true, "Kubernetes namespace to be used for deployment.")]
public string KubeNamespace { get; set; } = string.Empty;
[Uniform("nodes", "n", "NODES", true, "Number of Codex nodes to be created.")]
public int? NumberOfCodexNodes { get; set; }
[Uniform("validators", "v", "VALIDATORS", true, "Number of Codex nodes that will be validating.")]
public int? NumberOfValidators { get; set; }
[Uniform("storage-quota", "s", "STORAGEQUOTA", true, "Storage quota in megabytes used by each Codex node.")]
public int? StorageQuota { get; set; }
[Uniform("log-level", "l", "LOGLEVEL", true, "Log level used by each Codex node. [Trace, Debug*, Info, Warn, Error]")]
public CodexLogLevel CodexLogLevel { get; set; }
public TestRunnerLocation RunnerLocation { get; set; } = TestRunnerLocation.InternalToCluster;
public class Defaults
{
ForEachProperty(onString: Print, onInt: Print);
public string CodexImage { get; set; } = CodexContainerRecipe.DockerImage;
public string GethImage { get; set; } = GethContainerRecipe.DockerImage;
public string ContractsImage { get; set; } = CodexContractsContainerRecipe.DockerImage;
public CodexLogLevel CodexLogLevel { get; set; } = CodexLogLevel.Debug;
}
public List<string> Validate()
@ -86,16 +85,5 @@ namespace CodexNetDeployer
errors.Add($"{variable} is must be set.");
}
}
private static void Print(string variable, string value)
{
Console.WriteLine($"\t{variable}: '{value}'");
}
private static void Print(string variable, int? value)
{
if (value != null) Print(variable, value.ToString()!);
else Print(variable, "<NONE>");
}
}
}

View File

@ -52,7 +52,7 @@ namespace CodexNetDeployer
{
var lifecycleConfig = new DistTestCore.Configuration
(
kubeConfigFile: config.KubeConfigFile,
kubeConfigFile: null, //config.KubeConfigFile,
logPath: "null",
logDebug: false,
dataFilesPath: "notUsed",
@ -62,7 +62,7 @@ namespace CodexNetDeployer
var kubeConfig = new KubernetesWorkflow.Configuration(
k8sNamespacePrefix: config.KubeNamespace,
kubeConfigFile: config.KubeConfigFile,
kubeConfigFile: null, //config.KubeConfigFile,
operationTimeout: timeset.K8sOperationTimeout(),
retryDelay: timeset.WaitForK8sServiceDelay());

View File

@ -1,9 +1,7 @@
using CodexNetDeployer;
using ArgsUniform;
using CodexNetDeployer;
using DistTestCore;
using DistTestCore.Codex;
using DistTestCore.Marketplace;
using Newtonsoft.Json;
using Utils;
using Configuration = CodexNetDeployer.Configuration;
public class Program
@ -11,46 +9,29 @@ public class Program
public static void Main(string[] args)
{
var nl = Environment.NewLine;
Console.WriteLine("CodexNetDeployer" + nl + nl);
var argOrVar = new ArgOrVar(args);
Console.WriteLine("CodexNetDeployer" + nl);
if (args.Any(a => a == "-h" || a == "--help" || a == "-?"))
{
argOrVar.PrintHelp();
PrintHelp();
return;
}
var location = TestRunnerLocation.InternalToCluster;
var uniformArgs = new ArgsUniform<Configuration>(new Configuration.Defaults(), args);
var config = uniformArgs.Parse(true);
if (args.Any(a => a == "--external"))
{
location = TestRunnerLocation.ExternalToCluster;
config.RunnerLocation = TestRunnerLocation.ExternalToCluster;
}
var config = new Configuration(
codexImage: argOrVar.Get(ArgOrVar.CodexImage, CodexContainerRecipe.DockerImage),
gethImage: argOrVar.Get(ArgOrVar.GethImage, GethContainerRecipe.DockerImage),
contractsImage: argOrVar.Get(ArgOrVar.ContractsImage, CodexContractsContainerRecipe.DockerImage),
kubeConfigFile: argOrVar.Get(ArgOrVar.KubeConfigFile),
kubeNamespace: argOrVar.Get(ArgOrVar.KubeNamespace),
numberOfCodexNodes: argOrVar.GetInt(ArgOrVar.NumberOfCodexNodes),
numberOfValidators: argOrVar.GetInt(ArgOrVar.NumberOfValidatorNodes),
storageQuota: argOrVar.GetInt(ArgOrVar.StorageQuota),
codexLogLevel: ParseEnum.Parse<CodexLogLevel>(argOrVar.Get(ArgOrVar.LogLevel, nameof(CodexLogLevel.Debug))),
runnerLocation: location
);
Console.WriteLine("Using:");
config.PrintConfig();
Console.WriteLine(nl);
var errors = config.Validate();
if (errors.Any())
{
Console.WriteLine($"Configuration errors: ({errors.Count})");
foreach ( var error in errors ) Console.WriteLine("\t" + error);
Console.WriteLine(nl);
argOrVar.PrintHelp();
PrintHelp();
return;
}
@ -63,4 +44,18 @@ public class Program
Console.WriteLine("Done!");
}
private static void PrintHelp()
{
var nl = Environment.NewLine;
Console.WriteLine("CodexNetDeployer allows you to easily deploy multiple Codex nodes in a Kubernetes cluster. " +
"The deployer will set up the required supporting services, deploy the Codex on-chain contracts, start and bootstrap the Codex instances. " +
"All Kubernetes objects will be created in the namespace provided, allowing you to easily find, modify, and delete them afterwards." + nl);
Console.WriteLine("CodexNetDeployer assumes you are running this tool from *inside* the Kubernetes cluster you want to deploy to. " +
"If you are not running this from a container inside the cluster, add the argument '--external'." + nl);
var uniformArgs = new ArgsUniform<Configuration>();
uniformArgs.PrintHelp();
}
}

File diff suppressed because one or more lines are too long

View File

@ -36,9 +36,9 @@ namespace ContinuousTests
catch { }
}
var logPath = Environment.GetEnvironmentVariable("LOGPATH");
var dataPath = Environment.GetEnvironmentVariable("DATAPATH");
var codexDeploymentJson = Environment.GetEnvironmentVariable("CODEXDEPLOYMENT");
var logPath = "logs";// Environment.GetEnvironmentVariable("LOGPATH");
var dataPath = "data";// Environment.GetEnvironmentVariable("DATAPATH");
var codexDeploymentJson = "C:\\Users\\Ben\\Desktop\\codex-deployment.json"; // Environment.GetEnvironmentVariable("CODEXDEPLOYMENT");
var keep = Environment.GetEnvironmentVariable("KEEPPASSEDTESTLOGS");
if (!string.IsNullOrEmpty(logPath) &&

View File

@ -19,7 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NethereumWorkflow", "Nether
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ContinuousTests", "ContinuousTests\ContinuousTests.csproj", "{025B7074-0A09-4FCC-9BB9-03AE2A961EA1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodexNetDeployer", "CodexNetDeployer\CodexNetDeployer.csproj", "{871CAF12-14BE-4509-BC6E-20FDF0B1083A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexNetDeployer", "CodexNetDeployer\CodexNetDeployer.csproj", "{871CAF12-14BE-4509-BC6E-20FDF0B1083A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArgsUniform", "ArgsUniform\ArgsUniform.csproj", "{634324B1-E359-42B4-A269-BDC429936B3C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -63,6 +65,10 @@ Global
{871CAF12-14BE-4509-BC6E-20FDF0B1083A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{871CAF12-14BE-4509-BC6E-20FDF0B1083A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{871CAF12-14BE-4509-BC6E-20FDF0B1083A}.Release|Any CPU.Build.0 = Release|Any CPU
{634324B1-E359-42B4-A269-BDC429936B3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{634324B1-E359-42B4-A269-BDC429936B3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{634324B1-E359-42B4-A269-BDC429936B3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{634324B1-E359-42B4-A269-BDC429936B3C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE