/// <inheritdoc/> public void WriteYaml(IEmitter emitter, object value, Type type) { emitter.Emit(new Scalar(null, NeonHelper.EnumToString(type, value))); }
/// <summary> /// Returns the serialized YAML value for a <see cref="JValue"/>. /// </summary> /// <param name="jValue">The value.</param> /// <returns>The serialized value.</returns> private static string GetYamlValue(JValue jValue) { switch (jValue.Type) { case JTokenType.Boolean: return(NeonHelper.ToBoolString((bool)jValue.Value)); case JTokenType.Integer: case JTokenType.Float: return(jValue.ToString()); case JTokenType.String: // Strings are a bit tricky: // // * Strings like "true", "yes", "on", "false", "no", or "off" will be single quoted so they won't // be misinterpreted as booleans. // // * Strings like "1234" or "123.4" that parse as a number will be single quoted so they // won't be misinterpreted as a number. // // * Strings with special characters will to be double quoted with appropriate escapes. // // * Otherwise, we can render the string without quotes. var value = (string)jValue.Value; // Handle reserved booleans. switch (value.ToLowerInvariant()) { case "true": case "false": case "yes": case "no": case "on": case "off": return($"'{value}'"); } // Handle strings that parse to a number. if (double.TryParse(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var number)) { return($"'{value}'"); } // Handle strings with special characters. if (value.IndexOfAny(specialYamlChars) != -1) { var sb = new StringBuilder(); sb.Append("\""); foreach (var ch in value) { switch (ch) { case '\r': sb.Append("\\r"); break; case '\n': sb.Append("\\n"); break; case '\'': sb.Append("'"); break; case '"': sb.Append("\\\""); break; default: sb.Append(ch); break; } } sb.Append("\""); return(sb.ToString()); } // We dont need to quote the string. return(value); case JTokenType.Guid: case JTokenType.TimeSpan: case JTokenType.Uri: return($"\"{jValue.Value}\""); case JTokenType.Null: return("null"); default: throw new NotImplementedException(); } }
/// <summary> /// <para> /// Returns a dictionary mapping optional Windows feature names to a <see cref="WindowsFeatureStatus"/> /// indicating feature installation status. /// </para> /// <note> /// This method requires elevated permissions. /// </note> /// </summary> /// <returns>The feature dictionary.</returns> /// <exception cref="InvalidOperationException">Thrown when not running on Windows.</exception> /// <exception cref="ExecuteException">Thrown when the current process doesn't have elevated permissions.</exception> /// <remarks> /// <note> /// The feature names are in English and the lookup is case-insensitive. /// </note> /// </remarks> public static Dictionary <string, WindowsFeatureStatus> GetWindowsOptionalFeatures() { EnsureWindows(); // We're going to use the DSIM.EXE app to list these. The table output // will look like: // // Deployment Image Servicing and Management tool // Version: 10.0.19041.844 // // Image Version: 10.0.19042.1083 // // Features listing for package : Microsoft-Windows-Foundation-Package~31bf3856ad364e35~amd64~~10.0.19041.1 // // // ------------------------------------------- | -------- // Feature Name | State // ------------------------------------------- | -------- // Printing - PrintToPDFServices-Features | Enabled // Printing - XPSServices-Features | Enabled // TelnetClient | Disabled // TFTP | Disabled // LegacyComponents | Disabled // DirectPlay | Disabled // Printing-Foundation-Features | Enabled // Printing-Foundation-InternetPrinting-Client | Enabled // // ... // // We're simply going to parse the lines with a pipe ("|"). var response = NeonHelper.ExecuteCapture("dism.exe", new object[] { "/Online", "/English", "/Get-Features", "/Format:table" }); response.EnsureSuccess(); var featureMap = new Dictionary <string, WindowsFeatureStatus>(StringComparer.InvariantCultureIgnoreCase); foreach (var line in response.OutputText.ToLines()) { if (!line.StartsWith("----") && line.Contains('|')) { var fields = line.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); var status = WindowsFeatureStatus.Unknown; fields[0] = fields[0].Trim(); fields[1] = fields[1].Trim(); if (fields[0] == "Feature Name") { continue; // Ignore column headers } switch (fields[1].ToLowerInvariant()) { case "disabled": status = WindowsFeatureStatus.Disabled; break; case "enabled": status = WindowsFeatureStatus.Enabled; break; case "enable pending": status = WindowsFeatureStatus.EnabledPending; break; } featureMap[fields[0]] = status; } } return(featureMap); }
/// <summary> /// Attempts to parse an environment variable as an eumeration, writting messages /// to the associated logger if one was passed to the constructor. /// </summary> /// <typeparam name="TEnum">The desired enumeration type.</typeparam> /// <param name="variable">The variable name.</param> /// <param name="defaultInput">The default value.</param> /// <param name="required">Optionally specifies that the variable is required to exist.</param> /// <param name="validator"> /// Optional validation function to be called to verify that the parsed variable /// value is valid. This should return <c>null</c> for valid values and an error /// message for invalid ones. /// </param> /// <returns>The parsed value.</returns> /// <exception cref="KeyNotFoundException">Thrown if the variable does not exists and <paramref name="required"/>=<c>true</c>.</exception> /// <exception cref="FormatException">Thrown if the variable could not be parsed or the <paramref name="validator"/> returned an error.</exception> public TEnum Get <TEnum>(string variable, TEnum defaultInput, bool required = false, Validator <TEnum> validator = null) where TEnum : Enum { return(Parse <TEnum>(variable, NeonHelper.EnumToString(defaultInput), EnumParser, required, validator)); }
/// <summary> /// Starts a process to run an executable file and then waits for the process to terminate. /// </summary> /// <param name="path">Path to the executable file.</param> /// <param name="args">Command line arguments (or <c>null</c>).</param> /// <param name="timeout"> /// Optional maximum time to wait for the process to complete or <c>null</c> to wait /// indefinitely. /// </param> /// <param name="process"> /// The optional <see cref="Process"/> instance to use to launch the process. /// </param> /// <returns>The process exit code.</returns> /// <exception cref="TimeoutException">Thrown if the process did not exit within the <paramref name="timeout"/> limit.</exception> /// <remarks> /// <note> /// If <paramref name="timeout"/> is exceeded and execution has not commpleted in time /// then a <see cref="TimeoutException"/> will be thrown and the process will be killed /// if it was created by this method. Process instances passed via the <paramref name="process"/> /// parameter will not be killed in this case. /// </note> /// </remarks> public static int Execute(string path, string args, TimeSpan?timeout = null, Process process = null) { var processInfo = new ProcessStartInfo(GetProgramPath(path), args ?? string.Empty); var killOnTimeout = process == null; if (process == null) { process = new Process(); } try { processInfo.UseShellExecute = false; processInfo.CreateNoWindow = true; processInfo.RedirectStandardError = true; processInfo.RedirectStandardOutput = true; processInfo.WorkingDirectory = Environment.CurrentDirectory; process.StartInfo = processInfo; process.EnableRaisingEvents = true; // Configure the sub-process STDOUT and STDERR streams to use // code page 1252 which simply passes byte values through. processInfo.StandardErrorEncoding = processInfo.StandardOutputEncoding = ByteEncoding.Instance; // Relay STDOUT and STDERR output from the child process // to this process's STDOUT and STDERR streams. // $todo(jefflill): // // This won't work properly for binary data streaming // back from the process because we're not going to be // sure whether the "line" was terminated by a CRLF or // just a CR. I'm not sure if there's a clean way to // address this in .NET code. // // https://github.com/nforgeio/neonKUBE/issues/461 var stdErrClosed = false; var stdOutClosed = false; process.ErrorDataReceived += (s, a) => { if (a.Data == null) { stdErrClosed = true; } else { Console.Error.WriteLine(a.Data); } }; process.OutputDataReceived += (s, a) => { if (a.Data == null) { stdOutClosed = true; } else { Console.Out.WriteLine(a.Data); } }; process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); if (!timeout.HasValue || timeout.Value >= TimeSpan.FromDays(1)) { NeonHelper.WaitFor(() => stdErrClosed && stdOutClosed, timeout: timeout ?? TimeSpan.FromDays(365), pollTime: TimeSpan.FromMilliseconds(250)); process.WaitForExit(); } else { NeonHelper.WaitFor(() => stdErrClosed && stdOutClosed, timeout: timeout.Value, pollTime: TimeSpan.FromMilliseconds(250)); process.WaitForExit((int)timeout.Value.TotalMilliseconds); if (!process.HasExited) { if (killOnTimeout) { process.Kill(); } throw new TimeoutException(string.Format("Process [{0}] execute has timed out.", path)); } } return(process.ExitCode); } finally { process.Dispose(); } }
/// <summary> /// Generates a 13 digit base-36 UUID including only lowercase ASCII /// characters and digits. This is useful for generating unique shorter /// names for Kubernetes objects etc. /// </summary> /// <param name="secure"> /// Optionally specifies that a cryptographically secure algorithm is /// is used to generate the UUID, rather than the default pseudo random /// generator. Cryptogrphically secure algorithms consume system entropy. /// </param> /// <returns>The new base-36 string.</returns> public static string CreateBase36Uuid(bool secure = true) { ulong value; // Generate a non-zero random long. do { byte[] randomBytes; if (secure) { randomBytes = NeonHelper.GetCryptoRandomBytes(8); } else { randomBytes = NeonHelper.PseudoRandomBytes(8); } value = 0; foreach (var b in randomBytes) { value = (value * 256) + b; } }while (value == 0); // Convert the value into a base-36 string. var sb = new StringBuilder(); while (value > 0) { var digit = (int)(value % 36); if (digit < 10) { sb.Append((char)('0' + digit)); } else { sb.Append((char)('a' + (digit - 10))); } value /= 36; } var result = sb.ToString(); // We may end up up with more than 13 digits because 72-bit values // have more possible values than 13 digit base-36 numbers. We'll // just take the last 13 digits just in case. // // We may also end up with fewer than 13 digits when the random number // generated is very small (should be quite rare). We'll prepend zero // digits in this case. if (result.Length > 13) { result = result.Substring(result.Length - 13); } else if (result.Length < 13) { result = new string('0', 13 - result.Length) + result; } return(result); }