align files to .editorconfig (#203)

* align files to .editorconfig

* fix space cause build failed
This commit is contained in:
Boshi Lian
2018-09-27 10:50:39 -07:00
committed by Brendan Burns
parent 09a8c8773a
commit 9372e3291f
51 changed files with 4610 additions and 4611 deletions

View File

@@ -23,7 +23,7 @@ namespace attach
private async static Task AttachToPod(IKubernetes client, V1Pod pod) {
var webSocket = await client.WebSocketNamespacedPodAttachAsync(pod.Metadata.Name, "default", pod.Spec.Containers[0].Name);
var demux = new StreamDemuxer(webSocket);
demux.Start();

View File

@@ -1,44 +1,44 @@
using System;
using System.Collections.Generic;
using k8s;
namespace simple
{
internal class PodList
{
private static void Main(string[] args)
{
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
IKubernetes client = new Kubernetes(config);
Console.WriteLine("Starting Request!");
var list = client.ListNamespacedService("default");
foreach (var item in list.Items)
{
Console.WriteLine("Pods for service: " + item.Metadata.Name);
Console.WriteLine("=-=-=-=-=-=-=-=-=-=-=");
if (item.Spec == null || item.Spec.Selector == null)
{
continue;
}
var labels = new List<string>();
foreach (var key in item.Spec.Selector)
{
labels.Add(key.Key + "=" + key.Value);
}
var labelStr = string.Join(",", labels.ToArray());
Console.WriteLine(labelStr);
var podList = client.ListNamespacedPod("default", labelSelector: labelStr);
foreach (var pod in podList.Items)
{
Console.WriteLine(pod.Metadata.Name);
}
if (podList.Items.Count == 0)
{
Console.WriteLine("Empty!");
}
Console.WriteLine();
}
}
}
}
using System;
using System.Collections.Generic;
using k8s;
namespace simple
{
internal class PodList
{
private static void Main(string[] args)
{
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
IKubernetes client = new Kubernetes(config);
Console.WriteLine("Starting Request!");
var list = client.ListNamespacedService("default");
foreach (var item in list.Items)
{
Console.WriteLine("Pods for service: " + item.Metadata.Name);
Console.WriteLine("=-=-=-=-=-=-=-=-=-=-=");
if (item.Spec == null || item.Spec.Selector == null)
{
continue;
}
var labels = new List<string>();
foreach (var key in item.Spec.Selector)
{
labels.Add(key.Key + "=" + key.Value);
}
var labelStr = string.Join(",", labels.ToArray());
Console.WriteLine(labelStr);
var podList = client.ListNamespacedPod("default", labelSelector: labelStr);
foreach (var pod in podList.Items)
{
Console.WriteLine(pod.Metadata.Name);
}
if (podList.Items.Count == 0)
{
Console.WriteLine("Empty!");
}
Console.WriteLine();
}
}
}
}

View File

@@ -1,87 +1,87 @@
using System;
using System.Net;
using System.Threading.Tasks;
using k8s;
using k8s.Models;
namespace @namespace
{
class NamespaceExample
{
static void ListNamespaces(IKubernetes client) {
var list = client.ListNamespace();
foreach (var item in list.Items) {
Console.WriteLine(item.Metadata.Name);
}
if (list.Items.Count == 0) {
Console.WriteLine("Empty!");
}
}
static async Task DeleteAsync(IKubernetes client, string name, int delayMillis) {
while (true) {
await Task.Delay(delayMillis);
try
{
using System;
using System.Net;
using System.Threading.Tasks;
using k8s;
using k8s.Models;
namespace @namespace
{
class NamespaceExample
{
static void ListNamespaces(IKubernetes client) {
var list = client.ListNamespace();
foreach (var item in list.Items) {
Console.WriteLine(item.Metadata.Name);
}
if (list.Items.Count == 0) {
Console.WriteLine("Empty!");
}
}
static async Task DeleteAsync(IKubernetes client, string name, int delayMillis) {
while (true) {
await Task.Delay(delayMillis);
try
{
await client.ReadNamespaceAsync(name);
} catch (AggregateException ex) {
foreach (var innerEx in ex.InnerExceptions) {
if (innerEx is Microsoft.Rest.HttpOperationException) {
var code = ((Microsoft.Rest.HttpOperationException)innerEx).Response.StatusCode;
if (code == HttpStatusCode.NotFound) {
return;
}
throw ex;
}
}
} catch (Microsoft.Rest.HttpOperationException ex) {
if (ex.Response.StatusCode == HttpStatusCode.NotFound) {
return;
}
throw ex;
}
}
}
static void Delete(IKubernetes client, string name, int delayMillis) {
DeleteAsync(client, name, delayMillis).Wait();
}
private static void Main(string[] args)
{
var k8SClientConfig = KubernetesClientConfiguration.BuildConfigFromConfigFile();
IKubernetes client = new Kubernetes(k8SClientConfig);
ListNamespaces(client);
var ns = new V1Namespace
{
Metadata = new V1ObjectMeta
{
Name = "test"
}
};
var result = client.CreateNamespace(ns);
Console.WriteLine(result);
ListNamespaces(client);
var status = client.DeleteNamespace(new V1DeleteOptions(), ns.Metadata.Name);
if (status.HasObject)
{
} catch (AggregateException ex) {
foreach (var innerEx in ex.InnerExceptions) {
if (innerEx is Microsoft.Rest.HttpOperationException) {
var code = ((Microsoft.Rest.HttpOperationException)innerEx).Response.StatusCode;
if (code == HttpStatusCode.NotFound) {
return;
}
throw ex;
}
}
} catch (Microsoft.Rest.HttpOperationException ex) {
if (ex.Response.StatusCode == HttpStatusCode.NotFound) {
return;
}
throw ex;
}
}
}
static void Delete(IKubernetes client, string name, int delayMillis) {
DeleteAsync(client, name, delayMillis).Wait();
}
private static void Main(string[] args)
{
var k8SClientConfig = KubernetesClientConfiguration.BuildConfigFromConfigFile();
IKubernetes client = new Kubernetes(k8SClientConfig);
ListNamespaces(client);
var ns = new V1Namespace
{
Metadata = new V1ObjectMeta
{
Name = "test"
}
};
var result = client.CreateNamespace(ns);
Console.WriteLine(result);
ListNamespaces(client);
var status = client.DeleteNamespace(new V1DeleteOptions(), ns.Metadata.Name);
if (status.HasObject)
{
var obj = status.ObjectView<V1Namespace>();
Console.WriteLine(obj.Status.Phase);
Delete(client, ns.Metadata.Name, 3 * 1000);
}
else
{
Console.WriteLine(status.Message);
}
else
{
Console.WriteLine(status.Message);
}
ListNamespaces(client);
}
}
}
ListNamespaces(client);
}
}
}

View File

@@ -1,25 +1,25 @@
using System;
using k8s;
namespace simple
{
internal class PodList
{
private static void Main(string[] args)
{
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
IKubernetes client = new Kubernetes(config);
Console.WriteLine("Starting Request!");
using System;
using k8s;
var list = client.ListNamespacedPod("default");
foreach (var item in list.Items)
{
Console.WriteLine(item.Metadata.Name);
}
if (list.Items.Count == 0)
{
Console.WriteLine("Empty!");
}
}
}
}
namespace simple
{
internal class PodList
{
private static void Main(string[] args)
{
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
IKubernetes client = new Kubernetes(config);
Console.WriteLine("Starting Request!");
var list = client.ListNamespacedPod("default");
foreach (var item in list.Items)
{
Console.WriteLine(item.Metadata.Name);
}
if (list.Items.Count == 0)
{
Console.WriteLine("Empty!");
}
}
}
}

View File

@@ -1,33 +1,33 @@
using System;
using System.Threading;
using k8s;
using k8s.Models;
namespace watch
{
internal class Program
{
private static void Main(string[] args)
{
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
IKubernetes client = new Kubernetes(config);
var podlistResp = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).Result;
using (podlistResp.Watch<V1Pod>((type, item) =>
{
Console.WriteLine("==on watch event==");
Console.WriteLine(type);
Console.WriteLine(item.Metadata.Name);
Console.WriteLine("==on watch event==");
}))
{
Console.WriteLine("press ctrl + c to stop watching");
var ctrlc = new ManualResetEventSlim(false);
Console.CancelKeyPress += (sender, eventArgs) => ctrlc.Set();
ctrlc.Wait();
}
}
}
}
using System;
using System.Threading;
using k8s;
using k8s.Models;
namespace watch
{
internal class Program
{
private static void Main(string[] args)
{
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
IKubernetes client = new Kubernetes(config);
var podlistResp = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).Result;
using (podlistResp.Watch<V1Pod>((type, item) =>
{
Console.WriteLine("==on watch event==");
Console.WriteLine(type);
Console.WriteLine(item.Metadata.Name);
Console.WriteLine("==on watch event==");
}))
{
Console.WriteLine("press ctrl + c to stop watching");
var ctrlc = new ManualResetEventSlim(false);
Console.CancelKeyPress += (sender, eventArgs) => ctrlc.Set();
ctrlc.Wait();
}
}
}
}

View File

@@ -1,24 +1,24 @@
namespace k8s.Exceptions
{
using System;
/// <summary>
/// The exception that is thrown when the kube config is invalid
/// </summary>
public class KubeConfigException : Exception
{
public KubeConfigException()
{
}
public KubeConfigException(string message)
: base(message)
{
}
public KubeConfigException(string message, Exception inner)
: base(message, inner)
{
}
}
}
namespace k8s.Exceptions
{
using System;
/// <summary>
/// The exception that is thrown when the kube config is invalid
/// </summary>
public class KubeConfigException : Exception
{
public KubeConfigException()
{
}
public KubeConfigException(string message)
: base(message)
{
}
public KubeConfigException(string message, Exception inner)
: base(message, inner)
{
}
}
}

View File

@@ -1,24 +1,24 @@
namespace k8s.Exceptions
{
using System;
/// <summary>
/// The exception that is thrown when there is a client exception
/// </summary>
public class KubernetesClientException : Exception
{
public KubernetesClientException()
{
}
public KubernetesClientException(string message)
: base(message)
{
}
public KubernetesClientException(string message, Exception inner)
: base(message, inner)
{
}
}
}
namespace k8s.Exceptions
{
using System;
/// <summary>
/// The exception that is thrown when there is a client exception
/// </summary>
public class KubernetesClientException : Exception
{
public KubernetesClientException()
{
}
public KubernetesClientException(string message)
: base(message)
{
}
public KubernetesClientException(string message, Exception inner)
: base(message, inner)
{
}
}
}

View File

@@ -1,128 +1,128 @@
using System;
namespace Fractions.Extensions {
internal static class MathExt {
/// <summary>
/// Checks for an even number.
/// </summary>
/// <param name="number"></param>
/// <returns><c>true</c> if the number is even.</returns>
public static bool IsEven(this long number) {
return (number & 1) == 0;
}
/// <summary>
/// Checks for an odd number.
/// </summary>
/// <param name="number"></param>
/// <returns><c>true</c> if the number is odd.</returns>
public static bool IsOdd(this long number) {
return (number & 1) != 0;
}
/// <summary>
/// Get the greatest common divisor (GCD) of <paramref name="a"/> and <paramref name="b"/>.
/// </summary>
/// <param name="a">First number.</param>
/// <param name="b">Second number.</param>
/// <returns>The largest positive integer that divides <paramref name="a"/> and <paramref name="b"/> without a remainder.</returns>
public static long GreatestCommonDivisor(long a, long b) {
a = Math.Abs(a);
b = Math.Abs(b);
if (a == 0) {
// ggT(0, b) = b
// Denn alles teilt durch 0.
return b;
}
if (b == 0) {
// ggT(a, 0) = a
return a;
}
if (a == 1 || b == 1) {
// trivial
return 1;
}
return a == b
? a // Beide Zahlen sind identisch, wir haben bereits den ggT gefunden.
: BinaryGreatestCommonDivisorAlgorithm(a, b);
}
private static long BinaryGreatestCommonDivisorAlgorithm(long a, long b) {
// Solange 'a' und 'b' beide gerade Zahlen sind, teile die Zahlen durch 2
// und merke wie oft dies möglich war in 'k'.
int k;
for (k = 0; (a | b).IsEven(); ++k) {
a >>= 1; // a = (a / 2);
b >>= 1; // b = (b / 2);
}
// Teile 'a' solange durch 2 bis die Zahl ungerade ist.
while (a.IsEven()) {
a >>= 1; // a = (a / 2);
}
// Ab hier ist 'a' definitiv ungerade. Für 'b' muss dies allerdings noch nicht gelten!
do {
// Teile 'b' solange durch 2 bis die Zahl ungerade ist.
while (b.IsEven()) {
b >>= 1; // b = (b / 2);
}
// 'a' und 'b' sind hier beide ungerade. Falls 'a' >= 'b'
// muss der Inhalt beider Variablen geswappt werden,
// damit die notwendige Subtraktion durchgeführt werden
// kann.
if (a > b) {
var temp = b;
b = a;
a = temp;
}
b = b - a;
} while (b != 0);
return a << k; // a * 2^k
}
/// <summary>
/// Get the least common multiple (LCM) of <paramref name="a"/> and <paramref name="b"/>.
/// </summary>
/// <param name="a">The first number.</param>
/// <param name="b">The second number.</param>
/// <returns>The smallest positive integer that is divisible by both <paramref name="a"/> and <paramref name="b"/> or 0 if either <paramref name="a"/> or <paramref name="b"/> is 0</returns>
/// <exception cref="ArgumentException">If <paramref name="a"/> and <paramref name="b"/> are 0</exception>
public static long LeastCommonMultiple(long a, long b) {
if (a == 0 && b == 0) {
throw new ArgumentException("The least common multiple is not defined if both numbers are zero.");
}
a = Math.Abs(a);
b = Math.Abs(b);
if (a == b) {
return a;
}
// Es gilt LCM(a,b) = (|a*b|) / GCD(a,b)
var gcd = GreatestCommonDivisor(a, b);
return a / gcd * b;
}
/// <summary>
/// Returns <c>true</c> if there are remaining digits after the decimal point.
/// </summary>
/// <param name="remainingDigits">A <see cref="double"/> value with possible remaining digits</param>
/// <returns><c>true</c> if <paramref name="remainingDigits"/> has digits after the decimal point</returns>
public static bool RemainingDigitsAfterTheDecimalPoint(double remainingDigits) {
return Math.Abs(remainingDigits - Math.Floor(remainingDigits)) > double.Epsilon;
}
}
}
using System;
namespace Fractions.Extensions {
internal static class MathExt {
/// <summary>
/// Checks for an even number.
/// </summary>
/// <param name="number"></param>
/// <returns><c>true</c> if the number is even.</returns>
public static bool IsEven(this long number) {
return (number & 1) == 0;
}
/// <summary>
/// Checks for an odd number.
/// </summary>
/// <param name="number"></param>
/// <returns><c>true</c> if the number is odd.</returns>
public static bool IsOdd(this long number) {
return (number & 1) != 0;
}
/// <summary>
/// Get the greatest common divisor (GCD) of <paramref name="a"/> and <paramref name="b"/>.
/// </summary>
/// <param name="a">First number.</param>
/// <param name="b">Second number.</param>
/// <returns>The largest positive integer that divides <paramref name="a"/> and <paramref name="b"/> without a remainder.</returns>
public static long GreatestCommonDivisor(long a, long b) {
a = Math.Abs(a);
b = Math.Abs(b);
if (a == 0) {
// ggT(0, b) = b
// Denn alles teilt durch 0.
return b;
}
if (b == 0) {
// ggT(a, 0) = a
return a;
}
if (a == 1 || b == 1) {
// trivial
return 1;
}
return a == b
? a // Beide Zahlen sind identisch, wir haben bereits den ggT gefunden.
: BinaryGreatestCommonDivisorAlgorithm(a, b);
}
private static long BinaryGreatestCommonDivisorAlgorithm(long a, long b) {
// Solange 'a' und 'b' beide gerade Zahlen sind, teile die Zahlen durch 2
// und merke wie oft dies möglich war in 'k'.
int k;
for (k = 0; (a | b).IsEven(); ++k) {
a >>= 1; // a = (a / 2);
b >>= 1; // b = (b / 2);
}
// Teile 'a' solange durch 2 bis die Zahl ungerade ist.
while (a.IsEven()) {
a >>= 1; // a = (a / 2);
}
// Ab hier ist 'a' definitiv ungerade. Für 'b' muss dies allerdings noch nicht gelten!
do {
// Teile 'b' solange durch 2 bis die Zahl ungerade ist.
while (b.IsEven()) {
b >>= 1; // b = (b / 2);
}
// 'a' und 'b' sind hier beide ungerade. Falls 'a' >= 'b'
// muss der Inhalt beider Variablen geswappt werden,
// damit die notwendige Subtraktion durchgeführt werden
// kann.
if (a > b) {
var temp = b;
b = a;
a = temp;
}
b = b - a;
} while (b != 0);
return a << k; // a * 2^k
}
/// <summary>
/// Get the least common multiple (LCM) of <paramref name="a"/> and <paramref name="b"/>.
/// </summary>
/// <param name="a">The first number.</param>
/// <param name="b">The second number.</param>
/// <returns>The smallest positive integer that is divisible by both <paramref name="a"/> and <paramref name="b"/> or 0 if either <paramref name="a"/> or <paramref name="b"/> is 0</returns>
/// <exception cref="ArgumentException">If <paramref name="a"/> and <paramref name="b"/> are 0</exception>
public static long LeastCommonMultiple(long a, long b) {
if (a == 0 && b == 0) {
throw new ArgumentException("The least common multiple is not defined if both numbers are zero.");
}
a = Math.Abs(a);
b = Math.Abs(b);
if (a == b) {
return a;
}
// Es gilt LCM(a,b) = (|a*b|) / GCD(a,b)
var gcd = GreatestCommonDivisor(a, b);
return a / gcd * b;
}
/// <summary>
/// Returns <c>true</c> if there are remaining digits after the decimal point.
/// </summary>
/// <param name="remainingDigits">A <see cref="double"/> value with possible remaining digits</param>
/// <returns><c>true</c> if <paramref name="remainingDigits"/> has digits after the decimal point</returns>
public static bool RemainingDigitsAfterTheDecimalPoint(double remainingDigits) {
return Math.Abs(remainingDigits - Math.Floor(remainingDigits)) > double.Epsilon;
}
}
}

View File

@@ -1,19 +1,19 @@
using System;
namespace Fractions.Formatter {
/// <summary>
/// Default <see cref="Fraction.ToString()"/> formatter.
/// </summary>
public class DefaultFractionFormatProvider : IFormatProvider {
/// <summary>
/// Singleton instance
/// </summary>
public static readonly IFormatProvider Instance = new DefaultFractionFormatProvider();
object IFormatProvider.GetFormat(Type formatType) {
return formatType == typeof (Fraction)
? DefaultFractionFormatter.Instance
: null;
}
}
}
using System;
namespace Fractions.Formatter {
/// <summary>
/// Default <see cref="Fraction.ToString()"/> formatter.
/// </summary>
public class DefaultFractionFormatProvider : IFormatProvider {
/// <summary>
/// Singleton instance
/// </summary>
public static readonly IFormatProvider Instance = new DefaultFractionFormatProvider();
object IFormatProvider.GetFormat(Type formatType) {
return formatType == typeof (Fraction)
? DefaultFractionFormatter.Instance
: null;
}
}
}

View File

@@ -1,95 +1,95 @@
using System;
using System.Globalization;
using System.Numerics;
using System.Text;
namespace Fractions.Formatter {
internal class DefaultFractionFormatter : ICustomFormatter {
public static readonly ICustomFormatter Instance = new DefaultFractionFormatter();
public string Format(string format, object arg, IFormatProvider formatProvider) {
if (arg == null) {
return string.Empty;
}
if (!(arg is Fraction)) {
throw new FormatException(string.Format("The type {0} is not supported.", arg.GetType()));
}
var fraction = (Fraction)arg;
if (string.IsNullOrEmpty(format) || format == "G") {
return FormatGeneral(fraction);
}
var sb = new StringBuilder(32);
foreach (var character in format) {
switch (character) {
case 'G':
sb.Append(FormatGeneral(fraction));
break;
case 'n':
sb.Append(fraction.Numerator.ToString(CultureInfo.InvariantCulture));
break;
case 'd':
sb.Append(fraction.Denominator.ToString(CultureInfo.InvariantCulture));
break;
case 'z':
sb.Append(FormatInteger(fraction));
break;
case 'r':
sb.Append(FormatRemainder(fraction));
break;
case 'm':
sb.Append(FormatMixed(fraction));
break;
default:
sb.Append(character);
break;
}
}
return sb.ToString();
}
private static string FormatMixed(Fraction fraction) {
if (BigInteger.Abs(fraction.Numerator) < BigInteger.Abs(fraction.Denominator)) {
return FormatGeneral(fraction);
}
var integer = fraction.Numerator / fraction.Denominator;
var remainder = Fraction.Abs(fraction - integer);
return remainder.IsZero
? integer.ToString(CultureInfo.InvariantCulture)
: string.Concat(
integer.ToString(CultureInfo.InvariantCulture),
" ",
FormatGeneral(remainder));
}
private static string FormatInteger(Fraction fraction) {
return (fraction.Numerator / fraction.Denominator)
.ToString(CultureInfo.InvariantCulture);
}
private static string FormatRemainder(Fraction fraction) {
if (BigInteger.Abs(fraction.Numerator) < BigInteger.Abs(fraction.Denominator)) {
return FormatGeneral(fraction);
}
var integer = fraction.Numerator / fraction.Denominator;
var remainder = fraction - integer;
return FormatGeneral(remainder);
}
private static string FormatGeneral(Fraction fraction) {
if (fraction.Denominator == BigInteger.One) {
return fraction.Numerator.ToString(CultureInfo.InvariantCulture);
}
return string.Concat(
fraction.Numerator.ToString(CultureInfo.InvariantCulture),
"/",
fraction.Denominator.ToString(CultureInfo.InvariantCulture));
}
}
}
using System;
using System.Globalization;
using System.Numerics;
using System.Text;
namespace Fractions.Formatter {
internal class DefaultFractionFormatter : ICustomFormatter {
public static readonly ICustomFormatter Instance = new DefaultFractionFormatter();
public string Format(string format, object arg, IFormatProvider formatProvider) {
if (arg == null) {
return string.Empty;
}
if (!(arg is Fraction)) {
throw new FormatException(string.Format("The type {0} is not supported.", arg.GetType()));
}
var fraction = (Fraction)arg;
if (string.IsNullOrEmpty(format) || format == "G") {
return FormatGeneral(fraction);
}
var sb = new StringBuilder(32);
foreach (var character in format) {
switch (character) {
case 'G':
sb.Append(FormatGeneral(fraction));
break;
case 'n':
sb.Append(fraction.Numerator.ToString(CultureInfo.InvariantCulture));
break;
case 'd':
sb.Append(fraction.Denominator.ToString(CultureInfo.InvariantCulture));
break;
case 'z':
sb.Append(FormatInteger(fraction));
break;
case 'r':
sb.Append(FormatRemainder(fraction));
break;
case 'm':
sb.Append(FormatMixed(fraction));
break;
default:
sb.Append(character);
break;
}
}
return sb.ToString();
}
private static string FormatMixed(Fraction fraction) {
if (BigInteger.Abs(fraction.Numerator) < BigInteger.Abs(fraction.Denominator)) {
return FormatGeneral(fraction);
}
var integer = fraction.Numerator / fraction.Denominator;
var remainder = Fraction.Abs(fraction - integer);
return remainder.IsZero
? integer.ToString(CultureInfo.InvariantCulture)
: string.Concat(
integer.ToString(CultureInfo.InvariantCulture),
" ",
FormatGeneral(remainder));
}
private static string FormatInteger(Fraction fraction) {
return (fraction.Numerator / fraction.Denominator)
.ToString(CultureInfo.InvariantCulture);
}
private static string FormatRemainder(Fraction fraction) {
if (BigInteger.Abs(fraction.Numerator) < BigInteger.Abs(fraction.Denominator)) {
return FormatGeneral(fraction);
}
var integer = fraction.Numerator / fraction.Denominator;
var remainder = fraction - integer;
return FormatGeneral(remainder);
}
private static string FormatGeneral(Fraction fraction) {
if (fraction.Denominator == BigInteger.One) {
return fraction.Numerator.ToString(CultureInfo.InvariantCulture);
}
return string.Concat(
fraction.Numerator.ToString(CultureInfo.InvariantCulture),
"/",
fraction.Denominator.ToString(CultureInfo.InvariantCulture));
}
}
}

View File

@@ -1,61 +1,61 @@
using System;
using System.Numerics;
namespace Fractions {
public partial struct Fraction
{
/// <summary>
/// Compares the calculated value with the supplied <paramref name="other"/>.
/// </summary>
/// <param name="other">Fraction that shall be compared with.</param>
/// <returns>
/// Less than 0 if <paramref name="other"/> is greater.
/// Zero (0) if both calculated values are equal.
/// Greater then zero (0) if <paramref name="other"/> less.</returns>
/// <exception cref="ArgumentException">If <paramref name="other"/> is not of type <see cref="Fraction"/>.</exception>
public int CompareTo(object other) {
if (other == null) {
return 1;
}
if (other.GetType() != typeof(Fraction)) {
throw new ArgumentException(
string.Format("The comparing instance must be of type {0}. The supplied argument is of type {1}", GetType(), other.GetType()), nameof(other));
}
return CompareTo((Fraction)other);
}
/// <summary>
/// Compares the calculated value with the supplied <paramref name="other"/>.
/// </summary>
/// <param name="other">Fraction that shall be compared with.</param>
/// <returns>
/// Less than 0 if <paramref name="other"/> is greater.
/// Zero (0) if both calculated values are equal.
/// Greater then zero (0) if <paramref name="other"/> less.</returns>
public int CompareTo(Fraction other) {
if (_denominator == other._denominator) {
return _numerator.CompareTo(other._numerator);
}
if (IsZero != other.IsZero) {
if (IsZero) {
return other.IsPositive ? -1 : 1;
}
return IsPositive ? 1 : -1;
}
var gcd = BigInteger.GreatestCommonDivisor(_denominator, other._denominator);
var thisMultiplier = BigInteger.Divide(_denominator, gcd);
var otherMultiplier = BigInteger.Divide(other._denominator, gcd);
var a = BigInteger.Multiply(_numerator, otherMultiplier);
var b = BigInteger.Multiply(other._numerator, thisMultiplier);
return a.CompareTo(b);
}
}
}
using System;
using System.Numerics;
namespace Fractions {
public partial struct Fraction
{
/// <summary>
/// Compares the calculated value with the supplied <paramref name="other"/>.
/// </summary>
/// <param name="other">Fraction that shall be compared with.</param>
/// <returns>
/// Less than 0 if <paramref name="other"/> is greater.
/// Zero (0) if both calculated values are equal.
/// Greater then zero (0) if <paramref name="other"/> less.</returns>
/// <exception cref="ArgumentException">If <paramref name="other"/> is not of type <see cref="Fraction"/>.</exception>
public int CompareTo(object other) {
if (other == null) {
return 1;
}
if (other.GetType() != typeof(Fraction)) {
throw new ArgumentException(
string.Format("The comparing instance must be of type {0}. The supplied argument is of type {1}", GetType(), other.GetType()), nameof(other));
}
return CompareTo((Fraction)other);
}
/// <summary>
/// Compares the calculated value with the supplied <paramref name="other"/>.
/// </summary>
/// <param name="other">Fraction that shall be compared with.</param>
/// <returns>
/// Less than 0 if <paramref name="other"/> is greater.
/// Zero (0) if both calculated values are equal.
/// Greater then zero (0) if <paramref name="other"/> less.</returns>
public int CompareTo(Fraction other) {
if (_denominator == other._denominator) {
return _numerator.CompareTo(other._numerator);
}
if (IsZero != other.IsZero) {
if (IsZero) {
return other.IsPositive ? -1 : 1;
}
return IsPositive ? 1 : -1;
}
var gcd = BigInteger.GreatestCommonDivisor(_denominator, other._denominator);
var thisMultiplier = BigInteger.Divide(_denominator, gcd);
var otherMultiplier = BigInteger.Divide(other._denominator, gcd);
var a = BigInteger.Multiply(_numerator, otherMultiplier);
var b = BigInteger.Multiply(other._numerator, thisMultiplier);
return a.CompareTo(b);
}
}
}

View File

@@ -1,123 +1,123 @@
using System;
using System.Numerics;
namespace Fractions {
public partial struct Fraction
{
/// <summary>
/// Create a fraction with <paramref name="numerator"/>, <paramref name="denominator"/> and the fraction' <paramref name="state"/>.
/// Warning: if you use unreduced values combined with a state of <see cref="FractionState.IsNormalized"/>
/// you will get wrong results when working with the fraction value.
/// </summary>
/// <param name="numerator"></param>
/// <param name="denominator"></param>
/// <param name="state"></param>
private Fraction(BigInteger numerator, BigInteger denominator, FractionState state) {
_numerator = numerator;
_denominator = denominator;
_state = state;
}
/// <summary>
/// Creates a normalized (reduced/simplified) fraction using <paramref name="numerator"/> and <paramref name="denominator"/>.
/// </summary>
/// <param name="numerator">Numerator</param>
/// <param name="denominator">Denominator</param>
public Fraction(BigInteger numerator, BigInteger denominator)
: this(numerator, denominator, true) { }
/// <summary>
/// Creates a normalized (reduced/simplified) or unnormalized fraction using <paramref name="numerator"/> and <paramref name="denominator"/>.
/// </summary>
/// <param name="numerator">Numerator</param>
/// <param name="denominator">Denominator</param>
/// <param name="normalize">If <c>true</c> the fraction will be created as reduced/simplified fraction.
/// This is recommended, especially if your applications requires that the results of the equality methods <see cref="object.Equals(object)"/>
/// and <see cref="IComparable.CompareTo"/> are always the same. (1/2 != 2/4)</param>
public Fraction(BigInteger numerator, BigInteger denominator, bool normalize) {
if (normalize) {
this = GetReducedFraction(numerator, denominator);
return;
}
_state = numerator.IsZero && denominator.IsZero
? FractionState.IsNormalized
: FractionState.Unknown;
_numerator = numerator;
_denominator = denominator;
}
/// <summary>
/// Creates a normalized fraction using a signed 32bit integer.
/// </summary>
/// <param name="numerator">integer value that will be used for the numerator. The denominator will be 1.</param>
public Fraction(int numerator) {
_numerator = new BigInteger(numerator);
_denominator = numerator != 0 ? BigInteger.One : BigInteger.Zero;
_state = FractionState.IsNormalized;
}
/// <summary>
/// Creates a normalized fraction using a signed 64bit integer.
/// </summary>
/// <param name="numerator">integer value that will be used for the numerator. The denominator will be 1.</param>
public Fraction(long numerator) {
_numerator = new BigInteger(numerator);
_denominator = numerator != 0 ? BigInteger.One : BigInteger.Zero;
_state = FractionState.IsNormalized;
}
/// <summary>
/// Creates a normalized fraction using a unsigned 32bit integer.
/// </summary>
/// <param name="numerator">integer value that will be used for the numerator. The denominator will be 1.</param>
public Fraction(uint numerator) {
_numerator = new BigInteger(numerator);
_denominator = numerator != 0 ? BigInteger.One : BigInteger.Zero;
_state = FractionState.IsNormalized;
}
/// <summary>
/// Creates a normalized fraction using a unsigned 64bit integer.
/// </summary>
/// <param name="numerator">integer value that will be used for the numerator. The denominator will be 1.</param>
public Fraction(ulong numerator) {
_numerator = new BigInteger(numerator);
_denominator = numerator != 0 ? BigInteger.One : BigInteger.Zero;
_state = FractionState.IsNormalized;
}
/// <summary>
/// Creates a normalized fraction using a big integer.
/// </summary>
/// <param name="numerator">big integer value that will be used for the numerator. The denominator will be 1.</param>
public Fraction(BigInteger numerator) {
_numerator = numerator;
_denominator = numerator.IsZero ? BigInteger.Zero : BigInteger.One;
_state = FractionState.IsNormalized;
}
/// <summary>
/// Creates a normalized fraction using a 64bit floating point value (double).
/// The value will not be rounded therefore you will probably get huge numbers as numerator und denominator.
/// <see cref="double"/> values are not able to store simple rational numbers like 0.2 or 0.3 - so please
/// don't be worried if the fraction looks weird. For more information visit
/// http://en.wikipedia.org/wiki/Floating_point
/// </summary>
/// <param name="value">Floating point value.</param>
public Fraction(double value) {
this = FromDouble(value);
}
/// <summary>
/// Creates a normalized fraction using a 128bit decimal value (decimal).
/// </summary>
/// <param name="value">Floating point value.</param>
public Fraction(decimal value) {
this = FromDecimal(value);
}
}
}
using System;
using System.Numerics;
namespace Fractions {
public partial struct Fraction
{
/// <summary>
/// Create a fraction with <paramref name="numerator"/>, <paramref name="denominator"/> and the fraction' <paramref name="state"/>.
/// Warning: if you use unreduced values combined with a state of <see cref="FractionState.IsNormalized"/>
/// you will get wrong results when working with the fraction value.
/// </summary>
/// <param name="numerator"></param>
/// <param name="denominator"></param>
/// <param name="state"></param>
private Fraction(BigInteger numerator, BigInteger denominator, FractionState state) {
_numerator = numerator;
_denominator = denominator;
_state = state;
}
/// <summary>
/// Creates a normalized (reduced/simplified) fraction using <paramref name="numerator"/> and <paramref name="denominator"/>.
/// </summary>
/// <param name="numerator">Numerator</param>
/// <param name="denominator">Denominator</param>
public Fraction(BigInteger numerator, BigInteger denominator)
: this(numerator, denominator, true) { }
/// <summary>
/// Creates a normalized (reduced/simplified) or unnormalized fraction using <paramref name="numerator"/> and <paramref name="denominator"/>.
/// </summary>
/// <param name="numerator">Numerator</param>
/// <param name="denominator">Denominator</param>
/// <param name="normalize">If <c>true</c> the fraction will be created as reduced/simplified fraction.
/// This is recommended, especially if your applications requires that the results of the equality methods <see cref="object.Equals(object)"/>
/// and <see cref="IComparable.CompareTo"/> are always the same. (1/2 != 2/4)</param>
public Fraction(BigInteger numerator, BigInteger denominator, bool normalize) {
if (normalize) {
this = GetReducedFraction(numerator, denominator);
return;
}
_state = numerator.IsZero && denominator.IsZero
? FractionState.IsNormalized
: FractionState.Unknown;
_numerator = numerator;
_denominator = denominator;
}
/// <summary>
/// Creates a normalized fraction using a signed 32bit integer.
/// </summary>
/// <param name="numerator">integer value that will be used for the numerator. The denominator will be 1.</param>
public Fraction(int numerator) {
_numerator = new BigInteger(numerator);
_denominator = numerator != 0 ? BigInteger.One : BigInteger.Zero;
_state = FractionState.IsNormalized;
}
/// <summary>
/// Creates a normalized fraction using a signed 64bit integer.
/// </summary>
/// <param name="numerator">integer value that will be used for the numerator. The denominator will be 1.</param>
public Fraction(long numerator) {
_numerator = new BigInteger(numerator);
_denominator = numerator != 0 ? BigInteger.One : BigInteger.Zero;
_state = FractionState.IsNormalized;
}
/// <summary>
/// Creates a normalized fraction using a unsigned 32bit integer.
/// </summary>
/// <param name="numerator">integer value that will be used for the numerator. The denominator will be 1.</param>
public Fraction(uint numerator) {
_numerator = new BigInteger(numerator);
_denominator = numerator != 0 ? BigInteger.One : BigInteger.Zero;
_state = FractionState.IsNormalized;
}
/// <summary>
/// Creates a normalized fraction using a unsigned 64bit integer.
/// </summary>
/// <param name="numerator">integer value that will be used for the numerator. The denominator will be 1.</param>
public Fraction(ulong numerator) {
_numerator = new BigInteger(numerator);
_denominator = numerator != 0 ? BigInteger.One : BigInteger.Zero;
_state = FractionState.IsNormalized;
}
/// <summary>
/// Creates a normalized fraction using a big integer.
/// </summary>
/// <param name="numerator">big integer value that will be used for the numerator. The denominator will be 1.</param>
public Fraction(BigInteger numerator) {
_numerator = numerator;
_denominator = numerator.IsZero ? BigInteger.Zero : BigInteger.One;
_state = FractionState.IsNormalized;
}
/// <summary>
/// Creates a normalized fraction using a 64bit floating point value (double).
/// The value will not be rounded therefore you will probably get huge numbers as numerator und denominator.
/// <see cref="double"/> values are not able to store simple rational numbers like 0.2 or 0.3 - so please
/// don't be worried if the fraction looks weird. For more information visit
/// http://en.wikipedia.org/wiki/Floating_point
/// </summary>
/// <param name="value">Floating point value.</param>
public Fraction(double value) {
this = FromDouble(value);
}
/// <summary>
/// Creates a normalized fraction using a 128bit decimal value (decimal).
/// </summary>
/// <param name="value">Floating point value.</param>
public Fraction(decimal value) {
this = FromDecimal(value);
}
}
}

View File

@@ -1,314 +1,314 @@
using System;
using System.Globalization;
using System.Numerics;
using Fractions.Extensions;
namespace Fractions {
public partial struct Fraction {
/// <summary>
/// Converts a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character is depending on the system culture).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <returns>A normalized <see cref="Fraction"/></returns>
public static Fraction FromString(string fractionString) {
return FromString(fractionString, NumberStyles.Number, null);
}
/// <summary>
/// Converts a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character depends on <paramref name="formatProvider"/>).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <param name="formatProvider">Provides culture specific information that will be used to parse the <paramref name="fractionString"/>.</param>
/// <returns>A normalized <see cref="Fraction"/></returns>
public static Fraction FromString(string fractionString, IFormatProvider formatProvider) {
return FromString(fractionString, NumberStyles.Number, formatProvider);
}
/// <summary>
/// Converts a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character depends on <paramref name="formatProvider"/>).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <param name="numberStyles">A bitwise combination of number styles that are allowed in <paramref name="fractionString"/>.</param>
/// <param name="formatProvider">Provides culture specific information that will be used to parse the <paramref name="fractionString"/>.</param>
/// <returns>A normalized <see cref="Fraction"/></returns>
public static Fraction FromString(string fractionString, NumberStyles numberStyles, IFormatProvider formatProvider) {
if (fractionString == null) {
throw new ArgumentNullException(nameof(fractionString));
}
if (!TryParse(fractionString, numberStyles, formatProvider, true, out Fraction fraction)) {
throw new FormatException(string.Format("The string '{0}' cannot be converted to fraction.", fractionString));
}
return fraction;
}
/// <summary>
/// Try to convert a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character depends on the system's culture).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <param name="fraction">A normalized <see cref="Fraction"/> if the method returns with <c>true</c>. Otherwise the value is invalid.</param>
/// <returns>
/// <para><c>true</c> if <paramref name="fractionString"/> was well formed. The parsing result will be written to <paramref name="fraction"/>. </para>
/// <para><c>false</c> if <paramref name="fractionString"/> was invalid.</para></returns>
public static bool TryParse(string fractionString, out Fraction fraction) {
return TryParse(fractionString, NumberStyles.Number, null, true, out fraction);
}
/// <summary>
/// Try to convert a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character depends on <paramref name="formatProvider"/>).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <param name="numberStyles">A bitwise combination of number styles that are allowed in <paramref name="fractionString"/>.</param>
/// <param name="formatProvider">Provides culture specific information that will be used to parse the <paramref name="fractionString"/>.</param>
/// <param name="fraction">A normalized <see cref="Fraction"/> if the method returns with <c>true</c>. Otherwise the value is invalid.</param>
/// <returns>
/// <para><c>true</c> if <paramref name="fractionString"/> was well formed. The parsing result will be written to <paramref name="fraction"/>. </para>
/// <para><c>false</c> if <paramref name="fractionString"/> was invalid.</para>
/// </returns>
public static bool TryParse(string fractionString, NumberStyles numberStyles, IFormatProvider formatProvider, out Fraction fraction) {
return TryParse(fractionString, numberStyles, formatProvider, true, out fraction);
}
/// <summary>
/// Try to convert a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character depends on <paramref name="formatProvider"/>).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <param name="numberStyles">A bitwise combination of number styles that are allowed in <paramref name="fractionString"/>.</param>
/// <param name="formatProvider">Provides culture specific information that will be used to parse the <paramref name="fractionString"/>.</param>
/// <param name="normalize">If <c>true</c> the parsed fraction will be reduced.</param>
/// <param name="fraction">A <see cref="Fraction"/> if the method returns with <c>true</c>. Otherwise the value is invalid.</param>
/// <returns>
/// <para><c>true</c> if <paramref name="fractionString"/> was well formed. The parsing result will be written to <paramref name="fraction"/>. </para>
/// <para><c>false</c> if <paramref name="fractionString"/> was invalid.</para>
/// </returns>
public static bool TryParse(string fractionString, NumberStyles numberStyles, IFormatProvider formatProvider, bool normalize, out Fraction fraction) {
if (fractionString == null) {
return CannotParse(out fraction);
}
var components = fractionString.Split('/');
if (components.Length == 1) {
return TryParseSingleNumber(components[0], numberStyles, formatProvider, out fraction);
}
if (components.Length >= 2) {
var numeratorString = components[0];
var denominatorString = components[1];
var withoutDecimalpoint = numberStyles & ~NumberStyles.AllowDecimalPoint;
if (!BigInteger.TryParse(
value: numeratorString,
style: withoutDecimalpoint,
provider: formatProvider,
result: out BigInteger numerator)
|| !BigInteger.TryParse(
value: denominatorString,
style: withoutDecimalpoint,
provider: formatProvider,
result: out BigInteger denominator)) {
return CannotParse(out fraction);
}
fraction = new Fraction(numerator, denominator, normalize);
return true;
}
// Technically it should not be possible to reach this line of code..
return CannotParse(out fraction);
}
/// <summary>
/// Try to convert a single number to a fraction. Example 34 or 4.5 (depending on the supplied culture used in <paramref name="formatProvider"/>)
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="number">A (decimal) number</param>
/// <param name="numberStyles">A bitwise combination of number styles that are allowed in <paramref name="number"/>.</param>
/// <param name="formatProvider">Provides culture specific information that will be used to parse the <paramref name="number"/>.</param>
/// <param name="fraction">A <see cref="Fraction"/> if the method returns with <c>true</c>. Otherwise the value is invalid.</param>
/// <returns>
/// <para><c>true</c> if <paramref name="number"/> was well formed. The parsing result will be written to <paramref name="fraction"/>. </para>
/// <para><c>false</c> if <paramref name="number"/> was invalid.</para>
/// </returns>
private static bool TryParseSingleNumber(string number, NumberStyles numberStyles, IFormatProvider formatProvider, out Fraction fraction) {
var numberFormatInfo = NumberFormatInfo.GetInstance(formatProvider);
if (number.Contains(numberFormatInfo.NumberDecimalSeparator)) {
if (!decimal.TryParse(number, numberStyles, formatProvider, out decimal decimalNumber)) {
return CannotParse(out fraction);
}
fraction = FromDecimal(decimalNumber);
return true;
}
var withoutDecimalpoint = numberStyles & ~NumberStyles.AllowDecimalPoint;
if (!BigInteger.TryParse(number, withoutDecimalpoint, formatProvider, out BigInteger numerator)) {
return CannotParse(out fraction);
}
fraction = new Fraction(numerator);
return true;
}
/// <summary>
/// Returns false. <paramref name="fraction"/> contains an invalid value.
/// </summary>
/// <param name="fraction">Returns <c>default()</c> of <see cref="Fraction"/></param>
/// <returns><c>false</c></returns>
private static bool CannotParse(out Fraction fraction) {
fraction = default(Fraction);
return false;
}
/// <summary>
/// Converts a floating point value to a fraction. The value will not be rounded therefore you will probably
/// get huge numbers as numerator und denominator. <see cref="double"/> values are not able to store simple rational
/// numbers like 0.2 or 0.3 - so please don't be worried if the fraction looks weird. For more information visit
/// http://en.wikipedia.org/wiki/Floating_point
/// </summary>
/// <param name="value">A floating point value.</param>
/// <returns>A fraction</returns>
/// <exception cref="InvalidNumberException">If <paramref name="value"/> is NaN (not a number) or infinite.</exception>
public static Fraction FromDouble(double value) {
if (double.IsNaN(value) || double.IsInfinity(value)) {
throw new InvalidNumberException();
}
// No rounding here! It will convert the actual number that is stored as double!
// See http://www.mpdvc.de/artikel/FloatingPoint.htm
const ulong SIGN_BIT = 0x8000000000000000;
const ulong EXPONENT_BITS = 0x7FF0000000000000;
const ulong MANTISSA = 0x000FFFFFFFFFFFFF;
const ulong MANTISSA_DIVISOR = 0x0010000000000000;
const ulong K = 1023;
var one = BigInteger.One;
// value = (-1 * sign) * (1 + 2^(-1) + 2^(-2) .. + 2^(-52)) * 2^(exponent-K)
var valueBits = unchecked((ulong) BitConverter.DoubleToInt64Bits(value));
if (valueBits == 0) {
// See IEEE 754
return Zero;
}
var isNegative = (valueBits & SIGN_BIT) == SIGN_BIT;
var mantissaBits = valueBits & MANTISSA;
// (exponent-K)
var exponent = (int) (((valueBits & EXPONENT_BITS) >> 52) - K);
// (1 + 2^(-1) + 2^(-2) .. + 2^(-52))
var mantissa = new Fraction(mantissaBits + MANTISSA_DIVISOR, MANTISSA_DIVISOR);
// 2^exponent
var factor = exponent < 0
? new Fraction(one, one << Math.Abs(exponent))
: new Fraction(one << exponent);
var result = mantissa * factor;
return isNegative
? result.Invert()
: result;
}
/// <summary>
/// Converts a floating point value to a fraction. The value will be rounded if possible.
/// </summary>
/// <param name="value">A floating point value.</param>
/// <returns>A fraction</returns>
/// <exception cref="InvalidNumberException">If <paramref name="value"/> is NaN (not a number) or infinite.</exception>
public static Fraction FromDoubleRounded(double value) {
if (double.IsNaN(value) || double.IsInfinity(value)) {
throw new InvalidNumberException();
}
// Null?
if (Math.Abs(value - 0.0) < double.Epsilon) {
return Zero;
}
// Inspired from Syed Mehroz Alam <smehrozalam@yahoo.com> http://www.geocities.ws/smehrozalam/source/fractioncs.txt
// .. who got it from http://ebookbrowse.com/confrac-pdf-d13212190
// or ftp://89.25.159.69/knm/ksiazki/reszta/homepage.smc.edu.kennedy_john/CONFRAC.pdf
var sign = Math.Sign(value);
var absoluteValue = Math.Abs(value);
var numerator = new BigInteger(absoluteValue);
var denominator = 1.0;
var remainingDigits = absoluteValue;
var previousDenominator = 0.0;
var breakCounter = 0;
while (MathExt.RemainingDigitsAfterTheDecimalPoint(remainingDigits)
&& Math.Abs(absoluteValue - (double) numerator / denominator) > double.Epsilon) {
remainingDigits = 1.0 / (remainingDigits - Math.Floor(remainingDigits));
var tmp = denominator;
denominator = Math.Floor(remainingDigits) * denominator + previousDenominator;
numerator = new BigInteger(absoluteValue * denominator + 0.5);
previousDenominator = tmp;
// See http://www.ozgrid.com/forum/archive/index.php/t-22530.html
if (++breakCounter > 594) {
break;
}
}
return new Fraction(
sign < 0 ? BigInteger.Negate(numerator) : numerator,
new BigInteger(denominator),
true);
}
/// <summary>
/// Converts a decimal value in a fraction. The value will not be rounded.
/// </summary>
/// <param name="value">A decimal value.</param>
/// <returns>A fraction.</returns>
public static Fraction FromDecimal(decimal value) {
if (value == decimal.Zero) {
return _zero;
}
if (value == decimal.One) {
return _one;
}
if (value == decimal.MinusOne) {
return _minus_one;
}
var bits = decimal.GetBits(value);
var low = BitConverter.GetBytes(bits[0]);
var middle = BitConverter.GetBytes(bits[1]);
var high = BitConverter.GetBytes(bits[2]);
var scale = BitConverter.GetBytes(bits[3]);
var exp = scale[2];
bool positiveSign = (scale[3] & 0x80) == 0;
// value = 0x00,high,middle,low / 10^exp
var numerator = new BigInteger(new byte[] {
low[0], low[1], low[2], low[3],
middle[0], middle[1], middle[2], middle[3],
high[0], high[1], high[2], high[3],
0x00
});
var denominator = BigInteger.Pow(10, exp);
return positiveSign
? new Fraction(numerator, denominator, true)
: new Fraction(BigInteger.Negate(numerator), denominator, true);
}
}
}
using System;
using System.Globalization;
using System.Numerics;
using Fractions.Extensions;
namespace Fractions {
public partial struct Fraction {
/// <summary>
/// Converts a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character is depending on the system culture).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <returns>A normalized <see cref="Fraction"/></returns>
public static Fraction FromString(string fractionString) {
return FromString(fractionString, NumberStyles.Number, null);
}
/// <summary>
/// Converts a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character depends on <paramref name="formatProvider"/>).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <param name="formatProvider">Provides culture specific information that will be used to parse the <paramref name="fractionString"/>.</param>
/// <returns>A normalized <see cref="Fraction"/></returns>
public static Fraction FromString(string fractionString, IFormatProvider formatProvider) {
return FromString(fractionString, NumberStyles.Number, formatProvider);
}
/// <summary>
/// Converts a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character depends on <paramref name="formatProvider"/>).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <param name="numberStyles">A bitwise combination of number styles that are allowed in <paramref name="fractionString"/>.</param>
/// <param name="formatProvider">Provides culture specific information that will be used to parse the <paramref name="fractionString"/>.</param>
/// <returns>A normalized <see cref="Fraction"/></returns>
public static Fraction FromString(string fractionString, NumberStyles numberStyles, IFormatProvider formatProvider) {
if (fractionString == null) {
throw new ArgumentNullException(nameof(fractionString));
}
if (!TryParse(fractionString, numberStyles, formatProvider, true, out Fraction fraction)) {
throw new FormatException(string.Format("The string '{0}' cannot be converted to fraction.", fractionString));
}
return fraction;
}
/// <summary>
/// Try to convert a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character depends on the system's culture).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <param name="fraction">A normalized <see cref="Fraction"/> if the method returns with <c>true</c>. Otherwise the value is invalid.</param>
/// <returns>
/// <para><c>true</c> if <paramref name="fractionString"/> was well formed. The parsing result will be written to <paramref name="fraction"/>. </para>
/// <para><c>false</c> if <paramref name="fractionString"/> was invalid.</para></returns>
public static bool TryParse(string fractionString, out Fraction fraction) {
return TryParse(fractionString, NumberStyles.Number, null, true, out fraction);
}
/// <summary>
/// Try to convert a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character depends on <paramref name="formatProvider"/>).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <param name="numberStyles">A bitwise combination of number styles that are allowed in <paramref name="fractionString"/>.</param>
/// <param name="formatProvider">Provides culture specific information that will be used to parse the <paramref name="fractionString"/>.</param>
/// <param name="fraction">A normalized <see cref="Fraction"/> if the method returns with <c>true</c>. Otherwise the value is invalid.</param>
/// <returns>
/// <para><c>true</c> if <paramref name="fractionString"/> was well formed. The parsing result will be written to <paramref name="fraction"/>. </para>
/// <para><c>false</c> if <paramref name="fractionString"/> was invalid.</para>
/// </returns>
public static bool TryParse(string fractionString, NumberStyles numberStyles, IFormatProvider formatProvider, out Fraction fraction) {
return TryParse(fractionString, numberStyles, formatProvider, true, out fraction);
}
/// <summary>
/// Try to convert a string to a fraction. Example: "3/4" or "4.5" (the decimal separator character depends on <paramref name="formatProvider"/>).
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="fractionString">A fraction or a (decimal) number. The numerator and denominator must be separated with a '/' (slash) character.</param>
/// <param name="numberStyles">A bitwise combination of number styles that are allowed in <paramref name="fractionString"/>.</param>
/// <param name="formatProvider">Provides culture specific information that will be used to parse the <paramref name="fractionString"/>.</param>
/// <param name="normalize">If <c>true</c> the parsed fraction will be reduced.</param>
/// <param name="fraction">A <see cref="Fraction"/> if the method returns with <c>true</c>. Otherwise the value is invalid.</param>
/// <returns>
/// <para><c>true</c> if <paramref name="fractionString"/> was well formed. The parsing result will be written to <paramref name="fraction"/>. </para>
/// <para><c>false</c> if <paramref name="fractionString"/> was invalid.</para>
/// </returns>
public static bool TryParse(string fractionString, NumberStyles numberStyles, IFormatProvider formatProvider, bool normalize, out Fraction fraction) {
if (fractionString == null) {
return CannotParse(out fraction);
}
var components = fractionString.Split('/');
if (components.Length == 1) {
return TryParseSingleNumber(components[0], numberStyles, formatProvider, out fraction);
}
if (components.Length >= 2) {
var numeratorString = components[0];
var denominatorString = components[1];
var withoutDecimalpoint = numberStyles & ~NumberStyles.AllowDecimalPoint;
if (!BigInteger.TryParse(
value: numeratorString,
style: withoutDecimalpoint,
provider: formatProvider,
result: out BigInteger numerator)
|| !BigInteger.TryParse(
value: denominatorString,
style: withoutDecimalpoint,
provider: formatProvider,
result: out BigInteger denominator)) {
return CannotParse(out fraction);
}
fraction = new Fraction(numerator, denominator, normalize);
return true;
}
// Technically it should not be possible to reach this line of code..
return CannotParse(out fraction);
}
/// <summary>
/// Try to convert a single number to a fraction. Example 34 or 4.5 (depending on the supplied culture used in <paramref name="formatProvider"/>)
/// If the number contains a decimal separator it will be parsed as <see cref="decimal"/>.
/// </summary>
/// <param name="number">A (decimal) number</param>
/// <param name="numberStyles">A bitwise combination of number styles that are allowed in <paramref name="number"/>.</param>
/// <param name="formatProvider">Provides culture specific information that will be used to parse the <paramref name="number"/>.</param>
/// <param name="fraction">A <see cref="Fraction"/> if the method returns with <c>true</c>. Otherwise the value is invalid.</param>
/// <returns>
/// <para><c>true</c> if <paramref name="number"/> was well formed. The parsing result will be written to <paramref name="fraction"/>. </para>
/// <para><c>false</c> if <paramref name="number"/> was invalid.</para>
/// </returns>
private static bool TryParseSingleNumber(string number, NumberStyles numberStyles, IFormatProvider formatProvider, out Fraction fraction) {
var numberFormatInfo = NumberFormatInfo.GetInstance(formatProvider);
if (number.Contains(numberFormatInfo.NumberDecimalSeparator)) {
if (!decimal.TryParse(number, numberStyles, formatProvider, out decimal decimalNumber)) {
return CannotParse(out fraction);
}
fraction = FromDecimal(decimalNumber);
return true;
}
var withoutDecimalpoint = numberStyles & ~NumberStyles.AllowDecimalPoint;
if (!BigInteger.TryParse(number, withoutDecimalpoint, formatProvider, out BigInteger numerator)) {
return CannotParse(out fraction);
}
fraction = new Fraction(numerator);
return true;
}
/// <summary>
/// Returns false. <paramref name="fraction"/> contains an invalid value.
/// </summary>
/// <param name="fraction">Returns <c>default()</c> of <see cref="Fraction"/></param>
/// <returns><c>false</c></returns>
private static bool CannotParse(out Fraction fraction) {
fraction = default(Fraction);
return false;
}
/// <summary>
/// Converts a floating point value to a fraction. The value will not be rounded therefore you will probably
/// get huge numbers as numerator und denominator. <see cref="double"/> values are not able to store simple rational
/// numbers like 0.2 or 0.3 - so please don't be worried if the fraction looks weird. For more information visit
/// http://en.wikipedia.org/wiki/Floating_point
/// </summary>
/// <param name="value">A floating point value.</param>
/// <returns>A fraction</returns>
/// <exception cref="InvalidNumberException">If <paramref name="value"/> is NaN (not a number) or infinite.</exception>
public static Fraction FromDouble(double value) {
if (double.IsNaN(value) || double.IsInfinity(value)) {
throw new InvalidNumberException();
}
// No rounding here! It will convert the actual number that is stored as double!
// See http://www.mpdvc.de/artikel/FloatingPoint.htm
const ulong SIGN_BIT = 0x8000000000000000;
const ulong EXPONENT_BITS = 0x7FF0000000000000;
const ulong MANTISSA = 0x000FFFFFFFFFFFFF;
const ulong MANTISSA_DIVISOR = 0x0010000000000000;
const ulong K = 1023;
var one = BigInteger.One;
// value = (-1 * sign) * (1 + 2^(-1) + 2^(-2) .. + 2^(-52)) * 2^(exponent-K)
var valueBits = unchecked((ulong) BitConverter.DoubleToInt64Bits(value));
if (valueBits == 0) {
// See IEEE 754
return Zero;
}
var isNegative = (valueBits & SIGN_BIT) == SIGN_BIT;
var mantissaBits = valueBits & MANTISSA;
// (exponent-K)
var exponent = (int) (((valueBits & EXPONENT_BITS) >> 52) - K);
// (1 + 2^(-1) + 2^(-2) .. + 2^(-52))
var mantissa = new Fraction(mantissaBits + MANTISSA_DIVISOR, MANTISSA_DIVISOR);
// 2^exponent
var factor = exponent < 0
? new Fraction(one, one << Math.Abs(exponent))
: new Fraction(one << exponent);
var result = mantissa * factor;
return isNegative
? result.Invert()
: result;
}
/// <summary>
/// Converts a floating point value to a fraction. The value will be rounded if possible.
/// </summary>
/// <param name="value">A floating point value.</param>
/// <returns>A fraction</returns>
/// <exception cref="InvalidNumberException">If <paramref name="value"/> is NaN (not a number) or infinite.</exception>
public static Fraction FromDoubleRounded(double value) {
if (double.IsNaN(value) || double.IsInfinity(value)) {
throw new InvalidNumberException();
}
// Null?
if (Math.Abs(value - 0.0) < double.Epsilon) {
return Zero;
}
// Inspired from Syed Mehroz Alam <smehrozalam@yahoo.com> http://www.geocities.ws/smehrozalam/source/fractioncs.txt
// .. who got it from http://ebookbrowse.com/confrac-pdf-d13212190
// or ftp://89.25.159.69/knm/ksiazki/reszta/homepage.smc.edu.kennedy_john/CONFRAC.pdf
var sign = Math.Sign(value);
var absoluteValue = Math.Abs(value);
var numerator = new BigInteger(absoluteValue);
var denominator = 1.0;
var remainingDigits = absoluteValue;
var previousDenominator = 0.0;
var breakCounter = 0;
while (MathExt.RemainingDigitsAfterTheDecimalPoint(remainingDigits)
&& Math.Abs(absoluteValue - (double) numerator / denominator) > double.Epsilon) {
remainingDigits = 1.0 / (remainingDigits - Math.Floor(remainingDigits));
var tmp = denominator;
denominator = Math.Floor(remainingDigits) * denominator + previousDenominator;
numerator = new BigInteger(absoluteValue * denominator + 0.5);
previousDenominator = tmp;
// See http://www.ozgrid.com/forum/archive/index.php/t-22530.html
if (++breakCounter > 594) {
break;
}
}
return new Fraction(
sign < 0 ? BigInteger.Negate(numerator) : numerator,
new BigInteger(denominator),
true);
}
/// <summary>
/// Converts a decimal value in a fraction. The value will not be rounded.
/// </summary>
/// <param name="value">A decimal value.</param>
/// <returns>A fraction.</returns>
public static Fraction FromDecimal(decimal value) {
if (value == decimal.Zero) {
return _zero;
}
if (value == decimal.One) {
return _one;
}
if (value == decimal.MinusOne) {
return _minus_one;
}
var bits = decimal.GetBits(value);
var low = BitConverter.GetBytes(bits[0]);
var middle = BitConverter.GetBytes(bits[1]);
var high = BitConverter.GetBytes(bits[2]);
var scale = BitConverter.GetBytes(bits[3]);
var exp = scale[2];
bool positiveSign = (scale[3] & 0x80) == 0;
// value = 0x00,high,middle,low / 10^exp
var numerator = new BigInteger(new byte[] {
low[0], low[1], low[2], low[3],
middle[0], middle[1], middle[2], middle[3],
high[0], high[1], high[2], high[3],
0x00
});
var denominator = BigInteger.Pow(10, exp);
return positiveSign
? new Fraction(numerator, denominator, true)
: new Fraction(BigInteger.Negate(numerator), denominator, true);
}
}
}

View File

@@ -1,96 +1,96 @@
using System;
using System.Numerics;
namespace Fractions {
public partial struct Fraction {
/// <summary>
/// Returns the fraction as signed 32bit integer.
/// </summary>
/// <returns>32bit signed integer</returns>
public int ToInt32() {
if (IsZero) {
return 0;
}
return (int) (Numerator / Denominator);
}
/// <summary>
/// Returns the fraction as signed 64bit integer.
/// </summary>
/// <returns>64bit signed integer</returns>
public long ToInt64() {
if (IsZero) {
return 0;
}
return (long) (Numerator / Denominator);
}
/// <summary>
/// Returns the fraction as unsigned 32bit integer.
/// </summary>
/// <returns>32bit unsigned integer</returns>
public uint ToUInt32() {
if (IsZero) {
return 0;
}
return (uint) (Numerator / Denominator);
}
/// <summary>
/// Returns the fraction as unsigned 64bit integer.
/// </summary>
/// <returns>64-Bit unsigned integer</returns>
public ulong ToUInt64() {
if (IsZero) {
return 0;
}
return (ulong) (Numerator / Denominator);
}
/// <summary>
/// Returns the fraction as BigInteger.
/// </summary>
/// <returns>BigInteger</returns>
public BigInteger ToBigInteger() {
if (IsZero) {
return BigInteger.Zero;
}
return Numerator / Denominator;
}
/// <summary>
/// Returns the fraction as (rounded!) decimal value.
/// </summary>
/// <returns>Decimal value</returns>
public decimal ToDecimal() {
if (IsZero) {
return decimal.Zero;
}
if (_numerator >= MIN_DECIMAL && _numerator <= MAX_DECIMAL && _denominator >= MIN_DECIMAL && _denominator <= MAX_DECIMAL) {
return (decimal) _numerator / (decimal) _denominator;
}
// numerator or denominator is too big. Lets try to split the calculation..
// Possible OverFlowException!
var withoutDecimalPlaces = (decimal) (_numerator / _denominator);
var remainder = _numerator % _denominator;
var lowpart = remainder * BigInteger.Pow(10, 28) / _denominator;
var decimalPlaces = (decimal) lowpart / (decimal) Math.Pow(10, 28);
return withoutDecimalPlaces + decimalPlaces;
}
/// <summary>
/// Returns the fraction as (rounded!) floating point value.
/// </summary>
/// <returns>A floating point value</returns>
public double ToDouble() {
if (IsZero) {
return 0;
}
return (double) Numerator / (double) Denominator;
}
}
}
using System;
using System.Numerics;
namespace Fractions {
public partial struct Fraction {
/// <summary>
/// Returns the fraction as signed 32bit integer.
/// </summary>
/// <returns>32bit signed integer</returns>
public int ToInt32() {
if (IsZero) {
return 0;
}
return (int) (Numerator / Denominator);
}
/// <summary>
/// Returns the fraction as signed 64bit integer.
/// </summary>
/// <returns>64bit signed integer</returns>
public long ToInt64() {
if (IsZero) {
return 0;
}
return (long) (Numerator / Denominator);
}
/// <summary>
/// Returns the fraction as unsigned 32bit integer.
/// </summary>
/// <returns>32bit unsigned integer</returns>
public uint ToUInt32() {
if (IsZero) {
return 0;
}
return (uint) (Numerator / Denominator);
}
/// <summary>
/// Returns the fraction as unsigned 64bit integer.
/// </summary>
/// <returns>64-Bit unsigned integer</returns>
public ulong ToUInt64() {
if (IsZero) {
return 0;
}
return (ulong) (Numerator / Denominator);
}
/// <summary>
/// Returns the fraction as BigInteger.
/// </summary>
/// <returns>BigInteger</returns>
public BigInteger ToBigInteger() {
if (IsZero) {
return BigInteger.Zero;
}
return Numerator / Denominator;
}
/// <summary>
/// Returns the fraction as (rounded!) decimal value.
/// </summary>
/// <returns>Decimal value</returns>
public decimal ToDecimal() {
if (IsZero) {
return decimal.Zero;
}
if (_numerator >= MIN_DECIMAL && _numerator <= MAX_DECIMAL && _denominator >= MIN_DECIMAL && _denominator <= MAX_DECIMAL) {
return (decimal) _numerator / (decimal) _denominator;
}
// numerator or denominator is too big. Lets try to split the calculation..
// Possible OverFlowException!
var withoutDecimalPlaces = (decimal) (_numerator / _denominator);
var remainder = _numerator % _denominator;
var lowpart = remainder * BigInteger.Pow(10, 28) / _denominator;
var decimalPlaces = (decimal) lowpart / (decimal) Math.Pow(10, 28);
return withoutDecimalPlaces + decimalPlaces;
}
/// <summary>
/// Returns the fraction as (rounded!) floating point value.
/// </summary>
/// <returns>A floating point value</returns>
public double ToDouble() {
if (IsZero) {
return 0;
}
return (double) Numerator / (double) Denominator;
}
}
}

View File

@@ -1,63 +1,63 @@

namespace Fractions {
public partial struct Fraction
{
/// <summary>
/// Tests if the calculated value of this fraction equals to the calculated value of <paramref name="other"/>.
/// It does not matter if either of them is not normalized. Both values will be reduced (normalized) before performing
/// the <see cref="object.Equals(object)"/> test.
/// </summary>
/// <param name="other">The fraction to compare with.</param>
/// <returns><c>true</c> if both values are equivalent. (e.g. 2/4 is equivalent to 1/2. But 2/4 is not equivalent to -1/2)</returns>
public bool IsEquivalentTo(Fraction other) {
var a = Reduce();
var b = other.Reduce();
return a.Equals(b);
}
/// <summary>
/// <para>Performs an exact comparison with <paramref name="other"/> using numerator and denominator.</para>
/// <para>Warning: 1/2 is NOT equal to 2/4! -1/2 is NOT equal to 1/-2!</para>
/// <para>If you want to test the calculated values for equality use <see cref="CompareTo(Fraction)"/> or
/// <see cref="IsEquivalentTo"/> </para>
/// </summary>
/// <param name="other">The fraction to compare with.</param>
/// <returns><c>true</c> if numerator and denominator of both fractions are equal.</returns>
public bool Equals(Fraction other) {
return other._denominator.Equals(_denominator) && other._numerator.Equals(_numerator);
}
/// <summary>
/// <para>Performs an exact comparison with <paramref name="other"/> using numerator and denominator.</para>
/// <para>Warning: 1/2 is NOT equal to 2/4! -1/2 is NOT equal to 1/-2!</para>
/// <para>If you want to test the calculated values for equality use <see cref="CompareTo(Fraction)"/> or
/// <see cref="IsEquivalentTo"/> </para>
/// </summary>
/// <param name="other">The fraction to compare with.</param>
/// <returns><c>true</c> if <paramref name="other"/> is type of <see cref="Fraction"/> and numerator and denominator of both are equal.</returns>
public override bool Equals(object other) {
if (ReferenceEquals(null, other)) {
return false;
}
return other is Fraction && Equals((Fraction)other);
}
/// <summary>
/// Returns the hash code.
/// </summary>
/// <returns>
/// A 32bit integer with sign. It has been constructed using the <see cref="Numerator"/> and the <see cref="Denominator"/>.
/// </returns>
/// <filterpriority>2</filterpriority>
public override int GetHashCode() {
unchecked {
return (_denominator.GetHashCode() * 397) ^ _numerator.GetHashCode();
}
}
}
}
namespace Fractions {
public partial struct Fraction
{
/// <summary>
/// Tests if the calculated value of this fraction equals to the calculated value of <paramref name="other"/>.
/// It does not matter if either of them is not normalized. Both values will be reduced (normalized) before performing
/// the <see cref="object.Equals(object)"/> test.
/// </summary>
/// <param name="other">The fraction to compare with.</param>
/// <returns><c>true</c> if both values are equivalent. (e.g. 2/4 is equivalent to 1/2. But 2/4 is not equivalent to -1/2)</returns>
public bool IsEquivalentTo(Fraction other) {
var a = Reduce();
var b = other.Reduce();
return a.Equals(b);
}
/// <summary>
/// <para>Performs an exact comparison with <paramref name="other"/> using numerator and denominator.</para>
/// <para>Warning: 1/2 is NOT equal to 2/4! -1/2 is NOT equal to 1/-2!</para>
/// <para>If you want to test the calculated values for equality use <see cref="CompareTo(Fraction)"/> or
/// <see cref="IsEquivalentTo"/> </para>
/// </summary>
/// <param name="other">The fraction to compare with.</param>
/// <returns><c>true</c> if numerator and denominator of both fractions are equal.</returns>
public bool Equals(Fraction other) {
return other._denominator.Equals(_denominator) && other._numerator.Equals(_numerator);
}
/// <summary>
/// <para>Performs an exact comparison with <paramref name="other"/> using numerator and denominator.</para>
/// <para>Warning: 1/2 is NOT equal to 2/4! -1/2 is NOT equal to 1/-2!</para>
/// <para>If you want to test the calculated values for equality use <see cref="CompareTo(Fraction)"/> or
/// <see cref="IsEquivalentTo"/> </para>
/// </summary>
/// <param name="other">The fraction to compare with.</param>
/// <returns><c>true</c> if <paramref name="other"/> is type of <see cref="Fraction"/> and numerator and denominator of both are equal.</returns>
public override bool Equals(object other) {
if (ReferenceEquals(null, other)) {
return false;
}
return other is Fraction && Equals((Fraction)other);
}
/// <summary>
/// Returns the hash code.
/// </summary>
/// <returns>
/// A 32bit integer with sign. It has been constructed using the <see cref="Numerator"/> and the <see cref="Denominator"/>.
/// </returns>
/// <filterpriority>2</filterpriority>
public override int GetHashCode() {
unchecked {
return (_denominator.GetHashCode() * 397) ^ _numerator.GetHashCode();
}
}
}
}

View File

@@ -1,192 +1,192 @@
using System;
using System.Numerics;
namespace Fractions {
public partial struct Fraction {
/// <summary>
/// Calculates the remainder of the division with the fraction's value and the supplied <paramref name="divisor"/> (% operator).
/// </summary>
/// <param name="divisor">Divisor</param>
/// <returns>The remainder (left over)</returns>
public Fraction Remainder(Fraction divisor) {
if (divisor.IsZero) {
throw new DivideByZeroException();
}
if (IsZero) {
return _zero;
}
var gcd = BigInteger.GreatestCommonDivisor(_denominator, divisor.Denominator);
var thisMultiplier = BigInteger.Divide(_denominator, gcd);
var otherMultiplier = BigInteger.Divide(divisor.Denominator, gcd);
var leastCommonMultiple = BigInteger.Multiply(thisMultiplier, divisor.Denominator);
var a = BigInteger.Multiply(_numerator, otherMultiplier);
var b = BigInteger.Multiply(divisor.Numerator, thisMultiplier);
var remainder = BigInteger.Remainder(a, b);
return new Fraction(remainder, leastCommonMultiple);
}
/// <summary>
/// Adds the fraction's value with <paramref name="summand"/>.
/// </summary>
/// <param name="summand">Summand</param>
/// <returns>The result as summation.</returns>
public Fraction Add(Fraction summand) {
if (_denominator == summand.Denominator) {
return new Fraction(BigInteger.Add(_numerator, summand.Numerator), _denominator, true);
}
if (IsZero) {
// 0 + b = b
return summand;
}
if (summand.IsZero) {
// a + 0 = a
return this;
}
var gcd = BigInteger.GreatestCommonDivisor(_denominator, summand.Denominator);
var thisMultiplier = BigInteger.Divide(_denominator, gcd);
var otherMultiplier = BigInteger.Divide(summand.Denominator, gcd);
var leastCommonMultiple = BigInteger.Multiply(thisMultiplier, summand.Denominator);
var calculatedNumerator = BigInteger.Add(
BigInteger.Multiply(_numerator, otherMultiplier),
BigInteger.Multiply(summand.Numerator, thisMultiplier)
);
return new Fraction(calculatedNumerator, leastCommonMultiple, true);
}
/// <summary>
/// Subtracts the fraction's value (minuend) with <paramref name="subtrahend"/>.
/// </summary>
/// <param name="subtrahend">Subtrahend.</param>
/// <returns>The result as difference.</returns>
public Fraction Subtract(Fraction subtrahend) {
return Add(subtrahend.Invert());
}
/// <summary>
/// Inverts the fraction. Has the same result as multiplying it by -1.
/// </summary>
/// <returns>The inverted fraction.</returns>
public Fraction Invert() {
if (IsZero) {
return _zero;
}
return new Fraction(BigInteger.Negate(_numerator), _denominator, _state);
}
/// <summary>
/// Multiply the fraction's value by <paramref name="factor"/>.
/// </summary>
/// <param name="factor">Factor</param>
/// <returns>The result as product.</returns>
public Fraction Multiply(Fraction factor) {
return new Fraction(
_numerator * factor._numerator,
_denominator * factor._denominator,
true);
}
/// <summary>
/// Divides the fraction's value by <paramref name="divisor"/>.
/// </summary>
/// <param name="divisor">Divisor</param>
/// <returns>The result as quotient.</returns>
public Fraction Divide(Fraction divisor) {
if (divisor.IsZero) {
throw new DivideByZeroException(string.Format("{0} shall be divided by zero.", this));
}
return new Fraction(
numerator: _numerator * divisor._denominator,
denominator: _denominator * divisor._numerator,
normalize: true);
}
/// <summary>
/// Returns this as reduced/simplified fraction. The fraction's sign will be normalized.
/// </summary>
/// <returns>A reduced and normalized fraction.</returns>
public Fraction Reduce() {
return _state == FractionState.IsNormalized
? this
: GetReducedFraction(_numerator, _denominator);
}
/// <summary>
/// Gets the absolute value of a <see cref="Fraction"/> object.
/// </summary>
/// <returns>The absolute value.</returns>
public Fraction Abs() {
return Abs(this);
}
/// <summary>
/// Gets the absolute value of a <see cref="Fraction"/> object.
/// </summary>
/// <param name="fraction">The fraction.</param>
/// <returns>The absolute value.</returns>
public static Fraction Abs(Fraction fraction) {
return new Fraction(BigInteger.Abs(fraction.Numerator), BigInteger.Abs(fraction.Denominator), fraction.State);
}
/// <summary>
/// Returns a reduced and normalized fraction.
/// </summary>
/// <param name="numerator">Numerator</param>
/// <param name="denominator">Denominator</param>
/// <returns>A reduced and normalized fraction</returns>
public static Fraction GetReducedFraction(BigInteger numerator, BigInteger denominator) {
if (numerator.IsZero || denominator.IsZero) {
return Zero;
}
if (denominator.Sign == -1) {
// Denominator must not be negative after normalization
numerator = BigInteger.Negate(numerator);
denominator = BigInteger.Negate(denominator);
}
var gcd = BigInteger.GreatestCommonDivisor(numerator, denominator);
if (!gcd.IsOne && !gcd.IsZero) {
return new Fraction(BigInteger.Divide(numerator, gcd), BigInteger.Divide(denominator, gcd),
FractionState.IsNormalized);
}
return new Fraction(numerator, denominator, FractionState.IsNormalized);
}
/// <summary>
/// Returns a fraction raised to the specified power.
/// </summary>
/// <param name="base">base to be raised to a power</param>
/// <param name="exponent">A number that specifies a power (exponent)</param>
/// <returns>The fraction <paramref name="base"/> raised to the power <paramref name="exponent"/>.</returns>
public static Fraction Pow(Fraction @base, int exponent) {
return exponent < 0
? Pow(new Fraction(@base._denominator, @base._numerator), -exponent)
: new Fraction(BigInteger.Pow(@base._numerator, exponent), BigInteger.Pow(@base._denominator, exponent));
}
}
}
using System;
using System.Numerics;
namespace Fractions {
public partial struct Fraction {
/// <summary>
/// Calculates the remainder of the division with the fraction's value and the supplied <paramref name="divisor"/> (% operator).
/// </summary>
/// <param name="divisor">Divisor</param>
/// <returns>The remainder (left over)</returns>
public Fraction Remainder(Fraction divisor) {
if (divisor.IsZero) {
throw new DivideByZeroException();
}
if (IsZero) {
return _zero;
}
var gcd = BigInteger.GreatestCommonDivisor(_denominator, divisor.Denominator);
var thisMultiplier = BigInteger.Divide(_denominator, gcd);
var otherMultiplier = BigInteger.Divide(divisor.Denominator, gcd);
var leastCommonMultiple = BigInteger.Multiply(thisMultiplier, divisor.Denominator);
var a = BigInteger.Multiply(_numerator, otherMultiplier);
var b = BigInteger.Multiply(divisor.Numerator, thisMultiplier);
var remainder = BigInteger.Remainder(a, b);
return new Fraction(remainder, leastCommonMultiple);
}
/// <summary>
/// Adds the fraction's value with <paramref name="summand"/>.
/// </summary>
/// <param name="summand">Summand</param>
/// <returns>The result as summation.</returns>
public Fraction Add(Fraction summand) {
if (_denominator == summand.Denominator) {
return new Fraction(BigInteger.Add(_numerator, summand.Numerator), _denominator, true);
}
if (IsZero) {
// 0 + b = b
return summand;
}
if (summand.IsZero) {
// a + 0 = a
return this;
}
var gcd = BigInteger.GreatestCommonDivisor(_denominator, summand.Denominator);
var thisMultiplier = BigInteger.Divide(_denominator, gcd);
var otherMultiplier = BigInteger.Divide(summand.Denominator, gcd);
var leastCommonMultiple = BigInteger.Multiply(thisMultiplier, summand.Denominator);
var calculatedNumerator = BigInteger.Add(
BigInteger.Multiply(_numerator, otherMultiplier),
BigInteger.Multiply(summand.Numerator, thisMultiplier)
);
return new Fraction(calculatedNumerator, leastCommonMultiple, true);
}
/// <summary>
/// Subtracts the fraction's value (minuend) with <paramref name="subtrahend"/>.
/// </summary>
/// <param name="subtrahend">Subtrahend.</param>
/// <returns>The result as difference.</returns>
public Fraction Subtract(Fraction subtrahend) {
return Add(subtrahend.Invert());
}
/// <summary>
/// Inverts the fraction. Has the same result as multiplying it by -1.
/// </summary>
/// <returns>The inverted fraction.</returns>
public Fraction Invert() {
if (IsZero) {
return _zero;
}
return new Fraction(BigInteger.Negate(_numerator), _denominator, _state);
}
/// <summary>
/// Multiply the fraction's value by <paramref name="factor"/>.
/// </summary>
/// <param name="factor">Factor</param>
/// <returns>The result as product.</returns>
public Fraction Multiply(Fraction factor) {
return new Fraction(
_numerator * factor._numerator,
_denominator * factor._denominator,
true);
}
/// <summary>
/// Divides the fraction's value by <paramref name="divisor"/>.
/// </summary>
/// <param name="divisor">Divisor</param>
/// <returns>The result as quotient.</returns>
public Fraction Divide(Fraction divisor) {
if (divisor.IsZero) {
throw new DivideByZeroException(string.Format("{0} shall be divided by zero.", this));
}
return new Fraction(
numerator: _numerator * divisor._denominator,
denominator: _denominator * divisor._numerator,
normalize: true);
}
/// <summary>
/// Returns this as reduced/simplified fraction. The fraction's sign will be normalized.
/// </summary>
/// <returns>A reduced and normalized fraction.</returns>
public Fraction Reduce() {
return _state == FractionState.IsNormalized
? this
: GetReducedFraction(_numerator, _denominator);
}
/// <summary>
/// Gets the absolute value of a <see cref="Fraction"/> object.
/// </summary>
/// <returns>The absolute value.</returns>
public Fraction Abs() {
return Abs(this);
}
/// <summary>
/// Gets the absolute value of a <see cref="Fraction"/> object.
/// </summary>
/// <param name="fraction">The fraction.</param>
/// <returns>The absolute value.</returns>
public static Fraction Abs(Fraction fraction) {
return new Fraction(BigInteger.Abs(fraction.Numerator), BigInteger.Abs(fraction.Denominator), fraction.State);
}
/// <summary>
/// Returns a reduced and normalized fraction.
/// </summary>
/// <param name="numerator">Numerator</param>
/// <param name="denominator">Denominator</param>
/// <returns>A reduced and normalized fraction</returns>
public static Fraction GetReducedFraction(BigInteger numerator, BigInteger denominator) {
if (numerator.IsZero || denominator.IsZero) {
return Zero;
}
if (denominator.Sign == -1) {
// Denominator must not be negative after normalization
numerator = BigInteger.Negate(numerator);
denominator = BigInteger.Negate(denominator);
}
var gcd = BigInteger.GreatestCommonDivisor(numerator, denominator);
if (!gcd.IsOne && !gcd.IsZero) {
return new Fraction(BigInteger.Divide(numerator, gcd), BigInteger.Divide(denominator, gcd),
FractionState.IsNormalized);
}
return new Fraction(numerator, denominator, FractionState.IsNormalized);
}
/// <summary>
/// Returns a fraction raised to the specified power.
/// </summary>
/// <param name="base">base to be raised to a power</param>
/// <param name="exponent">A number that specifies a power (exponent)</param>
/// <returns>The fraction <paramref name="base"/> raised to the power <paramref name="exponent"/>.</returns>
public static Fraction Pow(Fraction @base, int exponent) {
return exponent < 0
? Pow(new Fraction(@base._denominator, @base._numerator), -exponent)
: new Fraction(BigInteger.Pow(@base._numerator, exponent), BigInteger.Pow(@base._denominator, exponent));
}
}
}

View File

@@ -1,111 +1,111 @@
using System.Numerics;
namespace Fractions {
public partial struct Fraction {
#pragma warning disable 1591
public static bool operator ==(Fraction left, Fraction right) {
return left.Equals(right);
}
public static bool operator !=(Fraction left, Fraction right) {
return !left.Equals(right);
}
public static Fraction operator +(Fraction a, Fraction b) {
return a.Add(b);
}
public static Fraction operator -(Fraction a, Fraction b) {
return a.Subtract(b);
}
public static Fraction operator *(Fraction a, Fraction b) {
return a.Multiply(b);
}
public static Fraction operator /(Fraction a, Fraction b) {
return a.Divide(b);
}
public static Fraction operator %(Fraction a, Fraction b) {
return a.Remainder(b);
}
public static bool operator <(Fraction a, Fraction b) {
return a.CompareTo(b) < 0;
}
public static bool operator >(Fraction a, Fraction b) {
return a.CompareTo(b) > 0;
}
public static bool operator <=(Fraction a, Fraction b) {
return a.CompareTo(b) <= 0;
}
public static bool operator >=(Fraction a, Fraction b) {
return a.CompareTo(b) >= 0;
}
public static implicit operator Fraction(int value) {
return new Fraction(value);
}
public static implicit operator Fraction(long value) {
return new Fraction(value);
}
public static implicit operator Fraction(uint value) {
return new Fraction(value);
}
public static implicit operator Fraction(ulong value) {
return new Fraction(value);
}
public static implicit operator Fraction(BigInteger value) {
return new Fraction(value);
}
public static explicit operator Fraction(double value) {
return new Fraction(value);
}
public static explicit operator Fraction(decimal value) {
return new Fraction(value);
}
public static explicit operator Fraction(string value) {
return FromString(value);
}
public static explicit operator int(Fraction fraction) {
return fraction.ToInt32();
}
public static explicit operator long(Fraction fraction) {
return fraction.ToInt64();
}
public static explicit operator uint(Fraction fraction) {
return fraction.ToUInt32();
}
public static explicit operator ulong(Fraction fraction) {
return fraction.ToUInt64();
}
public static explicit operator decimal(Fraction fraction) {
return fraction.ToDecimal();
}
public static explicit operator double(Fraction fraction) {
return fraction.ToDouble();
}
public static explicit operator BigInteger(Fraction fraction) {
return fraction.ToBigInteger();
}
#pragma warning restore 1591
}
}
using System.Numerics;
namespace Fractions {
public partial struct Fraction {
#pragma warning disable 1591
public static bool operator ==(Fraction left, Fraction right) {
return left.Equals(right);
}
public static bool operator !=(Fraction left, Fraction right) {
return !left.Equals(right);
}
public static Fraction operator +(Fraction a, Fraction b) {
return a.Add(b);
}
public static Fraction operator -(Fraction a, Fraction b) {
return a.Subtract(b);
}
public static Fraction operator *(Fraction a, Fraction b) {
return a.Multiply(b);
}
public static Fraction operator /(Fraction a, Fraction b) {
return a.Divide(b);
}
public static Fraction operator %(Fraction a, Fraction b) {
return a.Remainder(b);
}
public static bool operator <(Fraction a, Fraction b) {
return a.CompareTo(b) < 0;
}
public static bool operator >(Fraction a, Fraction b) {
return a.CompareTo(b) > 0;
}
public static bool operator <=(Fraction a, Fraction b) {
return a.CompareTo(b) <= 0;
}
public static bool operator >=(Fraction a, Fraction b) {
return a.CompareTo(b) >= 0;
}
public static implicit operator Fraction(int value) {
return new Fraction(value);
}
public static implicit operator Fraction(long value) {
return new Fraction(value);
}
public static implicit operator Fraction(uint value) {
return new Fraction(value);
}
public static implicit operator Fraction(ulong value) {
return new Fraction(value);
}
public static implicit operator Fraction(BigInteger value) {
return new Fraction(value);
}
public static explicit operator Fraction(double value) {
return new Fraction(value);
}
public static explicit operator Fraction(decimal value) {
return new Fraction(value);
}
public static explicit operator Fraction(string value) {
return FromString(value);
}
public static explicit operator int(Fraction fraction) {
return fraction.ToInt32();
}
public static explicit operator long(Fraction fraction) {
return fraction.ToInt64();
}
public static explicit operator uint(Fraction fraction) {
return fraction.ToUInt32();
}
public static explicit operator ulong(Fraction fraction) {
return fraction.ToUInt64();
}
public static explicit operator decimal(Fraction fraction) {
return fraction.ToDecimal();
}
public static explicit operator double(Fraction fraction) {
return fraction.ToDouble();
}
public static explicit operator BigInteger(Fraction fraction) {
return fraction.ToBigInteger();
}
#pragma warning restore 1591
}
}

View File

@@ -1,55 +1,55 @@
using System;
using System.Globalization;
using Fractions.Formatter;
namespace Fractions {
public partial struct Fraction {
/// <summary>
/// Returns the fraction as "numerator/denominator" or just "numerator" if the denominator has a value of 1.
/// The returning value is culture invariant (<see cref="CultureInfo" />).
/// </summary>
/// <returns>"numerator/denominator" or just "numerator"</returns>
public override string ToString() {
return ToString("G", DefaultFractionFormatProvider.Instance);
}
/// <summary>
/// Formats the value of the current instance using the specified format.
/// The returning value is culture invariant (<see cref="CultureInfo" />).
/// See <see cref="ToString(string,IFormatProvider)"/> for all formatting options.
/// </summary>
/// <returns>"numerator/denominator" or just "numerator"</returns>
public string ToString(string format) {
return ToString(format, DefaultFractionFormatProvider.Instance);
}
/// <summary>
/// Formats the value of the current instance using the specified format. The numbers are however culture invariant.
/// </summary>
/// <returns>
/// The value of the current instance in the specified format.
/// </returns>
/// <param name="format">The format to use.
/// <list type="table">
/// <listheader><term>symbol</term><description>description</description></listheader>
/// <item><term>G</term><description>General format: numerator/denominator</description></item>
/// <item><term>n</term><description>Numerator</description></item>
/// <item><term>d</term><description>Denominator</description></item>
/// <item><term>z</term><description>The fraction as integer</description></item>
/// <item><term>r</term><description>The positive remainder of all digits after the decimal point using the format: numerator/denominator or <see cref="string.Empty"/> if the fraction is a valid integer without digits after the decimal point.</description></item>
/// <item><term>m</term><description>The fraction as mixed number e.g. "2 1/3" instead of "7/3"</description></item>
/// </list>
/// -or- A null reference (Nothing in Visual Basic) to use the default format defined for the type of the <see cref="T:System.IFormattable"/> implementation. </param>
/// <param name="formatProvider">The provider to use to format the value. -or- A null reference (Nothing in Visual Basic) to obtain the numeric format information from the current locale setting of the operating system.</param>
/// <filterpriority>2</filterpriority>
public string ToString(string format, IFormatProvider formatProvider) {
var formatter = formatProvider?.GetFormat(GetType()) as ICustomFormatter;
return formatter != null
? formatter.Format(format, this, formatProvider)
: DefaultFractionFormatter.Instance.Format(format, this, formatProvider);
}
}
}
using System;
using System.Globalization;
using Fractions.Formatter;
namespace Fractions {
public partial struct Fraction {
/// <summary>
/// Returns the fraction as "numerator/denominator" or just "numerator" if the denominator has a value of 1.
/// The returning value is culture invariant (<see cref="CultureInfo" />).
/// </summary>
/// <returns>"numerator/denominator" or just "numerator"</returns>
public override string ToString() {
return ToString("G", DefaultFractionFormatProvider.Instance);
}
/// <summary>
/// Formats the value of the current instance using the specified format.
/// The returning value is culture invariant (<see cref="CultureInfo" />).
/// See <see cref="ToString(string,IFormatProvider)"/> for all formatting options.
/// </summary>
/// <returns>"numerator/denominator" or just "numerator"</returns>
public string ToString(string format) {
return ToString(format, DefaultFractionFormatProvider.Instance);
}
/// <summary>
/// Formats the value of the current instance using the specified format. The numbers are however culture invariant.
/// </summary>
/// <returns>
/// The value of the current instance in the specified format.
/// </returns>
/// <param name="format">The format to use.
/// <list type="table">
/// <listheader><term>symbol</term><description>description</description></listheader>
/// <item><term>G</term><description>General format: numerator/denominator</description></item>
/// <item><term>n</term><description>Numerator</description></item>
/// <item><term>d</term><description>Denominator</description></item>
/// <item><term>z</term><description>The fraction as integer</description></item>
/// <item><term>r</term><description>The positive remainder of all digits after the decimal point using the format: numerator/denominator or <see cref="string.Empty"/> if the fraction is a valid integer without digits after the decimal point.</description></item>
/// <item><term>m</term><description>The fraction as mixed number e.g. "2 1/3" instead of "7/3"</description></item>
/// </list>
/// -or- A null reference (Nothing in Visual Basic) to use the default format defined for the type of the <see cref="T:System.IFormattable"/> implementation. </param>
/// <param name="formatProvider">The provider to use to format the value. -or- A null reference (Nothing in Visual Basic) to obtain the numeric format information from the current locale setting of the operating system.</param>
/// <filterpriority>2</filterpriority>
public string ToString(string format, IFormatProvider formatProvider) {
var formatter = formatProvider?.GetFormat(GetType()) as ICustomFormatter;
return formatter != null
? formatter.Format(format, this, formatProvider)
: DefaultFractionFormatter.Instance.Format(format, this, formatProvider);
}
}
}

View File

@@ -1,81 +1,81 @@
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.InteropServices;
using Fractions.TypeConverters;
namespace Fractions {
/// <summary>
/// A mathematical fraction. A rational number written as a/b (a is the numerator and b the denominator).
/// The data type is not capable to store NaN (not a number) or infinite.
/// </summary>
[TypeConverter(typeof (FractionTypeConverter))]
[StructLayout(LayoutKind.Sequential)]
public partial struct Fraction : IEquatable<Fraction>, IComparable, IComparable<Fraction>, IFormattable {
private static readonly BigInteger MIN_DECIMAL = new BigInteger(decimal.MinValue);
private static readonly BigInteger MAX_DECIMAL = new BigInteger(decimal.MaxValue);
private static readonly Fraction _zero = new Fraction(BigInteger.Zero, BigInteger.Zero, FractionState.IsNormalized);
private static readonly Fraction _one = new Fraction(BigInteger.One, BigInteger.One, FractionState.IsNormalized);
private static readonly Fraction _minus_one = new Fraction(BigInteger.MinusOne, BigInteger.One, FractionState.IsNormalized);
private readonly BigInteger _denominator;
private readonly BigInteger _numerator;
private readonly FractionState _state;
/// <summary>
/// The numerator.
/// </summary>
public BigInteger Numerator => _numerator;
/// <summary>
/// The denominator
/// </summary>
public BigInteger Denominator => _denominator;
/// <summary>
/// <c>true</c> if the value is positive (greater than or equal to 0).
/// </summary>
public bool IsPositive => _numerator.Sign == 1 && _denominator.Sign == 1 ||
_numerator.Sign == -1 && _denominator.Sign == -1;
/// <summary>
/// <c>true</c> if the value is negative (lesser than 0).
/// </summary>
public bool IsNegative => _numerator.Sign == -1 && _denominator.Sign == 1 ||
_numerator.Sign == 1 && _denominator.Sign == -1;
/// <summary>
/// <c>true</c> if the fraction has a real (calculated) value of 0.
/// </summary>
public bool IsZero => _numerator.IsZero || _denominator.IsZero;
/// <summary>
/// The fraction's state.
/// </summary>
public FractionState State => _state;
/// <summary>
/// A fraction with the reduced/simplified value of 0.
/// </summary>
public static Fraction Zero => _zero;
/// <summary>
/// A fraction with the reduced/simplified value of 1.
/// </summary>
public static Fraction One => _one;
/// <summary>
/// A fraction with the reduced/simplified value of -1.
/// </summary>
public static Fraction MinusOne => _minus_one;
}
}
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.InteropServices;
using Fractions.TypeConverters;
namespace Fractions {
/// <summary>
/// A mathematical fraction. A rational number written as a/b (a is the numerator and b the denominator).
/// The data type is not capable to store NaN (not a number) or infinite.
/// </summary>
[TypeConverter(typeof (FractionTypeConverter))]
[StructLayout(LayoutKind.Sequential)]
public partial struct Fraction : IEquatable<Fraction>, IComparable, IComparable<Fraction>, IFormattable {
private static readonly BigInteger MIN_DECIMAL = new BigInteger(decimal.MinValue);
private static readonly BigInteger MAX_DECIMAL = new BigInteger(decimal.MaxValue);
private static readonly Fraction _zero = new Fraction(BigInteger.Zero, BigInteger.Zero, FractionState.IsNormalized);
private static readonly Fraction _one = new Fraction(BigInteger.One, BigInteger.One, FractionState.IsNormalized);
private static readonly Fraction _minus_one = new Fraction(BigInteger.MinusOne, BigInteger.One, FractionState.IsNormalized);
private readonly BigInteger _denominator;
private readonly BigInteger _numerator;
private readonly FractionState _state;
/// <summary>
/// The numerator.
/// </summary>
public BigInteger Numerator => _numerator;
/// <summary>
/// The denominator
/// </summary>
public BigInteger Denominator => _denominator;
/// <summary>
/// <c>true</c> if the value is positive (greater than or equal to 0).
/// </summary>
public bool IsPositive => _numerator.Sign == 1 && _denominator.Sign == 1 ||
_numerator.Sign == -1 && _denominator.Sign == -1;
/// <summary>
/// <c>true</c> if the value is negative (lesser than 0).
/// </summary>
public bool IsNegative => _numerator.Sign == -1 && _denominator.Sign == 1 ||
_numerator.Sign == 1 && _denominator.Sign == -1;
/// <summary>
/// <c>true</c> if the fraction has a real (calculated) value of 0.
/// </summary>
public bool IsZero => _numerator.IsZero || _denominator.IsZero;
/// <summary>
/// The fraction's state.
/// </summary>
public FractionState State => _state;
/// <summary>
/// A fraction with the reduced/simplified value of 0.
/// </summary>
public static Fraction Zero => _zero;
/// <summary>
/// A fraction with the reduced/simplified value of 1.
/// </summary>
public static Fraction One => _one;
/// <summary>
/// A fraction with the reduced/simplified value of -1.
/// </summary>
public static Fraction MinusOne => _minus_one;
}
}

View File

@@ -1,17 +1,17 @@
namespace Fractions {
/// <summary>
/// The fraction's state.
/// </summary>
public enum FractionState
{
/// <summary>
/// Unknown state.
/// </summary>
Unknown,
/// <summary>
/// A reduced/simplified fraction.
/// </summary>
IsNormalized
}
}
namespace Fractions {
/// <summary>
/// The fraction's state.
/// </summary>
public enum FractionState
{
/// <summary>
/// Unknown state.
/// </summary>
Unknown,
/// <summary>
/// A reduced/simplified fraction.
/// </summary>
IsNormalized
}
}

View File

@@ -1,14 +1,14 @@
using System;
namespace Fractions {
/// <summary>
/// Exception that will be thrown if an argument contains not a number (NaN) or is infinite.
/// </summary>
public class InvalidNumberException : ArithmeticException {
#pragma warning disable 1591
public InvalidNumberException() {}
public InvalidNumberException(string message) : base(message) {}
public InvalidNumberException(string message, Exception innerException) : base(message, innerException) {}
#pragma warning restore 1591
}
}
using System;
namespace Fractions {
/// <summary>
/// Exception that will be thrown if an argument contains not a number (NaN) or is infinite.
/// </summary>
public class InvalidNumberException : ArithmeticException {
#pragma warning disable 1591
public InvalidNumberException() {}
public InvalidNumberException(string message) : base(message) {}
public InvalidNumberException(string message, Exception innerException) : base(message, innerException) {}
#pragma warning restore 1591
}
}

View File

@@ -1,96 +1,96 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Numerics;
namespace Fractions.TypeConverters {
/// <summary>
/// Converts the <see cref="Fraction"/> from / to various data types.
/// </summary>
public sealed class FractionTypeConverter : TypeConverter {
private static readonly HashSet<Type> SUPPORTED_TYPES = new HashSet<Type> {
typeof (string),
typeof (int),
typeof (long),
typeof (decimal),
typeof (double),
typeof (Fraction),
typeof (BigInteger)
};
private static readonly Dictionary<Type, Func<object, CultureInfo, object>> CONVERT_TO_DICTIONARY =
new Dictionary<Type, Func<object, CultureInfo, object>> {
{typeof (string), (o, info) => ((Fraction) o).ToString()},
{typeof (int), (o, info) => ((Fraction) o).ToInt32()},
{typeof (long), (o, info) => ((Fraction) o).ToInt64()},
{typeof (decimal), (o, info) => ((Fraction) o).ToDecimal()},
{typeof (double), (o, info) => ((Fraction) o).ToDouble()},
{typeof (Fraction), (o, info) => (Fraction) o},
{typeof (BigInteger), (o, info) => ((Fraction) o).ToBigInteger()}
};
private static readonly Dictionary<Type, Func<object, CultureInfo, Fraction>> CONVERT_FROM_DICTIONARY =
new Dictionary<Type, Func<object, CultureInfo, Fraction>> {
{typeof (string), (o, info) => Fraction.FromString((string) o, info)},
{typeof (int), (o, info) => new Fraction((int) o)},
{typeof (long), (o, info) => new Fraction((long) o)},
{typeof (decimal), (o, info) => Fraction.FromDecimal((decimal) o)},
{typeof (double), (o, info) => Fraction.FromDouble((double) o)},
{typeof (Fraction), (o, info) => (Fraction) o},
{typeof (BigInteger), (o, info) => new Fraction((BigInteger) o)}
};
/// <summary>
/// Returns whether the type converter can convert an object to the specified type.
/// </summary>
/// <param name="context">An object that provides a format context.</param>
/// <param name="destinationType">The type you want to convert to.</param>
/// <returns><c>true</c> if this converter can perform the conversion; otherwise, <c>false</c>.</returns>
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
return SUPPORTED_TYPES.Contains(destinationType);
}
/// <summary>
/// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context.
/// </summary>
/// <param name="context">An <see cref="ITypeDescriptorContext "/>that provides a format context. </param>
/// <param name="sourceType">A <see cref="Type"/> that represents the type you want to convert from. </param>
/// <returns><c>true</c>if this converter can perform the conversion; otherwise, <c>false</c>.</returns>
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
return SUPPORTED_TYPES.Contains(sourceType);
}
/// <summary>
/// Converts the given value object to the specified type, using the specified context and culture information.
/// </summary>
/// <param name="context">An <see cref="ITypeDescriptorContext" /> that provides a format context.</param>
/// <param name="culture">A CultureInfo. If <c>null</c> is passed, the current culture is assumed.</param>
/// <param name="value">The <see cref="Object"/> to convert.</param>
/// <param name="destinationType">The <see cref="Type" /> to convert the value parameter to.</param>
/// <returns>An <see cref="Object"/> that represents the converted value.</returns>
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value,
Type destinationType) {
return !ReferenceEquals(value, null) && CONVERT_TO_DICTIONARY.TryGetValue(destinationType, out Func<object, CultureInfo, object> func)
? func(value, culture)
: base.ConvertTo(context, culture, value, destinationType);
}
/// <summary>
/// Converts the given object to the type of this converter, using the specified context and culture information.
/// </summary>
/// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param>
/// <param name="culture">The <see cref="CultureInfo"/> to use as the current culture.</param>
/// <param name="value">The <see cref="Object"/> to convert.</param>
/// <returns>An <see cref="Object"/> that represents the converted value.</returns>
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
if (ReferenceEquals(value, null)) {
return Fraction.Zero;
}
return CONVERT_FROM_DICTIONARY.TryGetValue(value.GetType(), out Func<object, CultureInfo, Fraction> func)
? func(value, culture)
: base.ConvertFrom(context, culture, value);
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Numerics;
namespace Fractions.TypeConverters {
/// <summary>
/// Converts the <see cref="Fraction"/> from / to various data types.
/// </summary>
public sealed class FractionTypeConverter : TypeConverter {
private static readonly HashSet<Type> SUPPORTED_TYPES = new HashSet<Type> {
typeof (string),
typeof (int),
typeof (long),
typeof (decimal),
typeof (double),
typeof (Fraction),
typeof (BigInteger)
};
private static readonly Dictionary<Type, Func<object, CultureInfo, object>> CONVERT_TO_DICTIONARY =
new Dictionary<Type, Func<object, CultureInfo, object>> {
{typeof (string), (o, info) => ((Fraction) o).ToString()},
{typeof (int), (o, info) => ((Fraction) o).ToInt32()},
{typeof (long), (o, info) => ((Fraction) o).ToInt64()},
{typeof (decimal), (o, info) => ((Fraction) o).ToDecimal()},
{typeof (double), (o, info) => ((Fraction) o).ToDouble()},
{typeof (Fraction), (o, info) => (Fraction) o},
{typeof (BigInteger), (o, info) => ((Fraction) o).ToBigInteger()}
};
private static readonly Dictionary<Type, Func<object, CultureInfo, Fraction>> CONVERT_FROM_DICTIONARY =
new Dictionary<Type, Func<object, CultureInfo, Fraction>> {
{typeof (string), (o, info) => Fraction.FromString((string) o, info)},
{typeof (int), (o, info) => new Fraction((int) o)},
{typeof (long), (o, info) => new Fraction((long) o)},
{typeof (decimal), (o, info) => Fraction.FromDecimal((decimal) o)},
{typeof (double), (o, info) => Fraction.FromDouble((double) o)},
{typeof (Fraction), (o, info) => (Fraction) o},
{typeof (BigInteger), (o, info) => new Fraction((BigInteger) o)}
};
/// <summary>
/// Returns whether the type converter can convert an object to the specified type.
/// </summary>
/// <param name="context">An object that provides a format context.</param>
/// <param name="destinationType">The type you want to convert to.</param>
/// <returns><c>true</c> if this converter can perform the conversion; otherwise, <c>false</c>.</returns>
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
return SUPPORTED_TYPES.Contains(destinationType);
}
/// <summary>
/// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context.
/// </summary>
/// <param name="context">An <see cref="ITypeDescriptorContext "/>that provides a format context. </param>
/// <param name="sourceType">A <see cref="Type"/> that represents the type you want to convert from. </param>
/// <returns><c>true</c>if this converter can perform the conversion; otherwise, <c>false</c>.</returns>
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
return SUPPORTED_TYPES.Contains(sourceType);
}
/// <summary>
/// Converts the given value object to the specified type, using the specified context and culture information.
/// </summary>
/// <param name="context">An <see cref="ITypeDescriptorContext" /> that provides a format context.</param>
/// <param name="culture">A CultureInfo. If <c>null</c> is passed, the current culture is assumed.</param>
/// <param name="value">The <see cref="Object"/> to convert.</param>
/// <param name="destinationType">The <see cref="Type" /> to convert the value parameter to.</param>
/// <returns>An <see cref="Object"/> that represents the converted value.</returns>
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value,
Type destinationType) {
return !ReferenceEquals(value, null) && CONVERT_TO_DICTIONARY.TryGetValue(destinationType, out Func<object, CultureInfo, object> func)
? func(value, culture)
: base.ConvertTo(context, culture, value, destinationType);
}
/// <summary>
/// Converts the given object to the type of this converter, using the specified context and culture information.
/// </summary>
/// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param>
/// <param name="culture">The <see cref="CultureInfo"/> to use as the current culture.</param>
/// <param name="value">The <see cref="Object"/> to convert.</param>
/// <returns>An <see cref="Object"/> that represents the converted value.</returns>
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
if (ReferenceEquals(value, null)) {
return Fraction.Zero;
}
return CONVERT_FROM_DICTIONARY.TryGetValue(value.GetType(), out Func<object, CultureInfo, Fraction> func)
? func(value, culture)
: base.ConvertFrom(context, culture, value);
}
}
}

View File

@@ -24,7 +24,7 @@ namespace k8s
/// </param>
/// <param name="limit">
/// limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.
///
///
/// The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.
/// </param>
/// <param name="pretty">

View File

@@ -9,7 +9,7 @@ namespace k8s
/// You can use the <see cref="KubernetesObject"/> if you receive JSON from a Kubernetes API server but
/// are unsure which object the API server is about to return. You can parse the JSON as a <see cref="KubernetesObject"/>
/// and use the <see cref="ApiVersion"/> and <see cref="Kind"/> properties to get basic metadata about any Kubernetes object.
/// You can then
/// You can then
/// </remarks>
public interface IKubernetesObject
{

View File

@@ -1,74 +1,74 @@
using System;
using Newtonsoft.Json;
namespace k8s.Models
{
internal class IntOrStringConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var s = (value as IntstrIntOrString)?.Value;
if (int.TryParse(s, out var intv))
{
serializer.Serialize(writer, intv);
return;
}
serializer.Serialize(writer, s);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
return (IntstrIntOrString) serializer.Deserialize<string>(reader);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(int) || objectType == typeof(string);
}
}
[JsonConverter(typeof(IntOrStringConverter))]
public partial class IntstrIntOrString
{
public static implicit operator int(IntstrIntOrString v)
{
return int.Parse(v.Value);
}
public static implicit operator IntstrIntOrString(int v)
{
return new IntstrIntOrString(Convert.ToString(v));
}
public static implicit operator string(IntstrIntOrString v)
{
return v.Value;
}
public static implicit operator IntstrIntOrString(string v)
{
return new IntstrIntOrString(v);
}
protected bool Equals(IntstrIntOrString other)
{
return string.Equals(Value, other.Value);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((IntstrIntOrString) obj);
}
public override int GetHashCode()
{
return (Value != null ? Value.GetHashCode() : 0);
}
}
}
using System;
using Newtonsoft.Json;
namespace k8s.Models
{
internal class IntOrStringConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var s = (value as IntstrIntOrString)?.Value;
if (int.TryParse(s, out var intv))
{
serializer.Serialize(writer, intv);
return;
}
serializer.Serialize(writer, s);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
return (IntstrIntOrString) serializer.Deserialize<string>(reader);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(int) || objectType == typeof(string);
}
}
[JsonConverter(typeof(IntOrStringConverter))]
public partial class IntstrIntOrString
{
public static implicit operator int(IntstrIntOrString v)
{
return int.Parse(v.Value);
}
public static implicit operator IntstrIntOrString(int v)
{
return new IntstrIntOrString(Convert.ToString(v));
}
public static implicit operator string(IntstrIntOrString v)
{
return v.Value;
}
public static implicit operator IntstrIntOrString(string v)
{
return new IntstrIntOrString(v);
}
protected bool Equals(IntstrIntOrString other)
{
return string.Equals(Value, other.Value);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((IntstrIntOrString) obj);
}
public override int GetHashCode()
{
return (Value != null ? Value.GetHashCode() : 0);
}
}
}

View File

@@ -21,4 +21,4 @@ namespace k8s.KubeConfigModels
public Dictionary<string, string> Config { get; set; }
}
}
}

View File

@@ -1,22 +1,22 @@
namespace k8s.KubeConfigModels
{
using YamlDotNet.Serialization;
namespace k8s.KubeConfigModels
{
using YamlDotNet.Serialization;
/// <summary>
/// Relates nicknames to cluster information.
/// </summary>
public class Cluster
/// <summary>
/// Relates nicknames to cluster information.
/// </summary>
public class Cluster
{
/// <summary>
/// Gets or sets the cluster information.
/// </summary>
[YamlMember(Alias = "cluster")]
public ClusterEndpoint ClusterEndpoint { get; set; }
/// <summary>
/// Gets or sets the cluster information.
/// </summary>
[YamlMember(Alias = "cluster")]
public ClusterEndpoint ClusterEndpoint { get; set; }
/// <summary>
/// Gets or sets the nickname for this Cluster.
/// </summary>
[YamlMember(Alias = "name")]
public string Name { get; set; }
}
}
/// <summary>
/// Gets or sets the nickname for this Cluster.
/// </summary>
[YamlMember(Alias = "name")]
public string Name { get; set; }
}
}

View File

@@ -1,42 +1,42 @@
namespace k8s.KubeConfigModels
namespace k8s.KubeConfigModels
{
using System.Collections.Generic;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization;
/// <summary>
/// Contains information about how to communicate with a kubernetes cluster
/// </summary>
public class ClusterEndpoint
/// <summary>
/// Contains information about how to communicate with a kubernetes cluster
/// </summary>
public class ClusterEndpoint
{
/// <summary>
/// Gets or sets the path to a cert file for the certificate authority.
/// <summary>
/// Gets or sets the path to a cert file for the certificate authority.
/// </summary>
[YamlMember(Alias = "certificate-authority", ApplyNamingConventions = false)]
public string CertificateAuthority {get; set; }
[YamlMember(Alias = "certificate-authority", ApplyNamingConventions = false)]
public string CertificateAuthority {get; set; }
/// <summary>
/// Gets or sets =PEM-encoded certificate authority certificates. Overrides <see cref="CertificateAuthority"/>.
/// </summary>
[YamlMember(Alias = "certificate-authority-data", ApplyNamingConventions = false)]
public string CertificateAuthorityData { get; set; }
/// <summary>
/// Gets or sets =PEM-encoded certificate authority certificates. Overrides <see cref="CertificateAuthority"/>.
/// </summary>
[YamlMember(Alias = "certificate-authority-data", ApplyNamingConventions = false)]
public string CertificateAuthorityData { get; set; }
/// <summary>
/// Gets or sets the address of the kubernetes cluster (https://hostname:port).
/// </summary>
[YamlMember(Alias = "server")]
public string Server { get; set; }
/// <summary>
/// Gets or sets the address of the kubernetes cluster (https://hostname:port).
/// </summary>
[YamlMember(Alias = "server")]
public string Server { get; set; }
/// <summary>
/// <summary>
/// Gets or sets a value indicating whether to skip the validity check for the server's certificate.
/// This will make your HTTPS connections insecure.
/// </summary>
[YamlMember(Alias = "insecure-skip-tls-verify", ApplyNamingConventions = false)]
/// This will make your HTTPS connections insecure.
/// </summary>
[YamlMember(Alias = "insecure-skip-tls-verify", ApplyNamingConventions = false)]
public bool SkipTlsVerify { get; set; }
/// <summary>
/// Gets or sets additional information. This is useful for extenders so that reads and writes don't clobber unknown fields.
/// <summary>
/// Gets or sets additional information. This is useful for extenders so that reads and writes don't clobber unknown fields.
/// </summary>
[YamlMember(Alias = "extensions")]
public IDictionary<string, dynamic> Extensions { get; set; }
}
}
public IDictionary<string, dynamic> Extensions { get; set; }
}
}

View File

@@ -1,25 +1,25 @@
namespace k8s.KubeConfigModels
{
using YamlDotNet.Serialization;
namespace k8s.KubeConfigModels
{
using YamlDotNet.Serialization;
/// <summary>
/// Relates nicknames to context information.
/// </summary>
public class Context
/// <summary>
/// Relates nicknames to context information.
/// </summary>
public class Context
{
/// <summary>
/// Gets or sets the context information.
/// </summary>
[YamlMember(Alias = "context")]
public ContextDetails ContextDetails { get; set; }
/// <summary>
/// Gets or sets the context information.
/// </summary>
[YamlMember(Alias = "context")]
public ContextDetails ContextDetails { get; set; }
/// <summary>
/// Gets or sets the nickname for this context.
/// </summary>
[YamlMember(Alias = "name")]
public string Name { get; set; }
[YamlMember(Alias = "namespace")]
public string Namespace { get; set; }
}
}
/// <summary>
/// Gets or sets the nickname for this context.
/// </summary>
[YamlMember(Alias = "name")]
public string Name { get; set; }
[YamlMember(Alias = "namespace")]
public string Namespace { get; set; }
}
}

View File

@@ -1,36 +1,36 @@
namespace k8s.KubeConfigModels
namespace k8s.KubeConfigModels
{
using System.Collections.Generic;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization;
/// <summary>
/// <summary>
/// Represents a tuple of references to a cluster (how do I communicate with a kubernetes cluster),
/// a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
/// </summary>
public class ContextDetails
/// a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
/// </summary>
public class ContextDetails
{
/// <summary>
/// Gets or sets the name of the cluster for this context.
/// </summary>
[YamlMember(Alias = "cluster")]
public string Cluster { get; set; }
/// <summary>
/// Gets or sets the name of the cluster for this context.
/// </summary>
[YamlMember(Alias = "cluster")]
public string Cluster { get; set; }
/// <summary>
/// Gets or sets the anem of the user for this context.
/// </summary>
[YamlMember(Alias = "user")]
public string User { get; set; }
/// <summary>
/// Gets or sets the anem of the user for this context.
/// </summary>
[YamlMember(Alias = "user")]
public string User { get; set; }
/// <summary>
/// /Gets or sets the default namespace to use on unspecified requests.
/// </summary>
[YamlMember(Alias = "namespace")]
/// <summary>
/// /Gets or sets the default namespace to use on unspecified requests.
/// </summary>
[YamlMember(Alias = "namespace")]
public string Namespace { get; set; }
/// <summary>
/// Gets or sets additional information. This is useful for extenders so that reads and writes don't clobber unknown fields.
/// <summary>
/// Gets or sets additional information. This is useful for extenders so that reads and writes don't clobber unknown fields.
/// </summary>
[YamlMember(Alias = "extensions")]
public IDictionary<string, dynamic> Extensions { get; set; }
}
}
public IDictionary<string, dynamic> Extensions { get; set; }
}
}

View File

@@ -1,64 +1,64 @@
namespace k8s.KubeConfigModels
{
using System.Collections.Generic;
using YamlDotNet.Serialization;
/// <summary>
namespace k8s.KubeConfigModels
{
using System.Collections.Generic;
using YamlDotNet.Serialization;
/// <summary>
/// kubeconfig configuration model. Holds the information needed to build connect to remote
/// Kubernetes clusters as a given user.
/// Kubernetes clusters as a given user.
/// </summary>
/// <remarks>
/// Should be kept in sync with https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go
/// </remarks>
public class K8SConfiguration
/// </remarks>
public class K8SConfiguration
{
/// <summary>
/// Gets or sets general information to be use for CLI interactions
/// </summary>
[YamlMember(Alias = "preferences")]
public IDictionary<string, object> Preferences{ get; set; }
[YamlMember(Alias = "apiVersion")]
public string ApiVersion { get; set; }
[YamlMember(Alias = "kind")]
public string Kind { get; set; }
/// <summary>
/// Gets or sets general information to be use for CLI interactions
/// </summary>
[YamlMember(Alias = "preferences")]
public IDictionary<string, object> Preferences{ get; set; }
/// <summary>
/// Gets or sets the name of the context that you would like to use by default.
/// </summary>
[YamlMember(Alias = "current-context", ApplyNamingConventions = false)]
public string CurrentContext { get; set; }
[YamlMember(Alias = "apiVersion")]
public string ApiVersion { get; set; }
/// <summary>
/// Gets or sets a map of referencable names to context configs.
/// </summary>
[YamlMember(Alias = "contexts")]
public IEnumerable<Context> Contexts { get; set; } = new Context[0];
[YamlMember(Alias = "kind")]
public string Kind { get; set; }
/// <summary>
/// Gets or sets a map of referencable names to cluster configs.
/// </summary>
[YamlMember(Alias = "clusters")]
public IEnumerable<Cluster> Clusters { get; set; } = new Cluster[0];
/// <summary>
/// Gets or sets the name of the context that you would like to use by default.
/// </summary>
[YamlMember(Alias = "current-context", ApplyNamingConventions = false)]
public string CurrentContext { get; set; }
/// <summary>
/// Gets or sets a map of referencable names to user configs
/// </summary>
[YamlMember(Alias = "users")]
/// <summary>
/// Gets or sets a map of referencable names to context configs.
/// </summary>
[YamlMember(Alias = "contexts")]
public IEnumerable<Context> Contexts { get; set; } = new Context[0];
/// <summary>
/// Gets or sets a map of referencable names to cluster configs.
/// </summary>
[YamlMember(Alias = "clusters")]
public IEnumerable<Cluster> Clusters { get; set; } = new Cluster[0];
/// <summary>
/// Gets or sets a map of referencable names to user configs
/// </summary>
[YamlMember(Alias = "users")]
public IEnumerable<User> Users { get; set; } = new User[0];
/// <summary>
/// Gets or sets additional information. This is useful for extenders so that reads and writes don't clobber unknown fields.
/// <summary>
/// Gets or sets additional information. This is useful for extenders so that reads and writes don't clobber unknown fields.
/// </summary>
[YamlMember(Alias = "extensions")]
public IDictionary<string, dynamic> Extensions { get; set; }
/// <summary>
/// <summary>
/// Gets or sets the name of the Kubernetes configuration file. This property is set only when the configuration
/// was loaded from disk, and can be used to resolve relative paths.
/// was loaded from disk, and can be used to resolve relative paths.
/// </summary>
[YamlIgnore]
public string FileName { get; set; }
}
}
public string FileName { get; set; }
}
}

View File

@@ -1,22 +1,22 @@
namespace k8s.KubeConfigModels
{
using YamlDotNet.Serialization;
namespace k8s.KubeConfigModels
{
using YamlDotNet.Serialization;
/// <summary>
/// Relates nicknames to auth information.
/// </summary>
public class User
/// <summary>
/// Relates nicknames to auth information.
/// </summary>
public class User
{
/// <summary>
/// Gets or sets the auth information.
/// </summary>
[YamlMember(Alias = "user")]
public UserCredentials UserCredentials { get; set; }
/// <summary>
/// Gets or sets the auth information.
/// </summary>
[YamlMember(Alias = "user")]
public UserCredentials UserCredentials { get; set; }
/// <summary>
/// Gets or sets the nickname for this auth information.
/// </summary>
[YamlMember(Alias = "name")]
public string Name { get; set; }
}
}
/// <summary>
/// Gets or sets the nickname for this auth information.
/// </summary>
[YamlMember(Alias = "name")]
public string Name { get; set; }
}
}

View File

@@ -1,84 +1,84 @@
namespace k8s.KubeConfigModels
{
using System.Collections.Generic;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
/// <summary>
/// Contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
/// </summary>
public class UserCredentials
{
/// <summary>
/// Gets or sets PEM-encoded data from a client cert file for TLS. Overrides <see cref="ClientCertificate"/>.
/// </summary>
[YamlMember(Alias = "client-certificate-data", ApplyNamingConventions = false)]
public string ClientCertificateData { get; set; }
/// <summary>
/// Gets or sets the path to a client cert file for TLS.
/// </summary>
[YamlMember(Alias = "client-certificate", ApplyNamingConventions = false)]
public string ClientCertificate { get; set; }
/// <summary>
/// Gets or sets PEM-encoded data from a client key file for TLS. Overrides <see cref="ClientKey"/>.
/// </summary>
[YamlMember(Alias = "client-key-data", ApplyNamingConventions = false)]
public string ClientKeyData { get; set; }
/// <summary>
/// Gets or sets the path to a client key file for TLS.
/// </summary>
[YamlMember(Alias = "client-key", ApplyNamingConventions = false)]
public string ClientKey { get; set; }
/// <summary>
/// Gets or sets the bearer token for authentication to the kubernetes cluster.
/// </summary>
[YamlMember(Alias = "token")]
public string Token { get; set; }
/// <summary>
/// Gets or sets the username to imperonate. The name matches the flag.
/// </summary>
[YamlMember(Alias = "as")]
public string Impersonate { get; set; }
/// <summary>
/// Gets or sets the groups to imperonate.
/// </summary>
[YamlMember(Alias = "as-groups", ApplyNamingConventions = false)]
public IEnumerable<string> ImpersonateGroups { get; set; } = new string[0];
/// <summary>
/// Gets or sets additional information for impersonated user.
/// </summary>
[YamlMember(Alias = "as-user-extra", ApplyNamingConventions = false)]
public Dictionary<string, string> ImpersonateUserExtra { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Gets or sets the username for basic authentication to the kubernetes cluster.
/// </summary>
[YamlMember(Alias = "username")]
public string UserName { get; set; }
/// <summary>
/// Gets or sets the password for basic authentication to the kubernetes cluster.
/// </summary>
[YamlMember(Alias = "password")]
public string Password { get; set; }
/// <summary>
/// Gets or sets custom authentication plugin for the kubernetes cluster.
/// </summary>
[YamlMember(Alias = "auth-provider", ApplyNamingConventions = false)]
public AuthProvider AuthProvider { get; set; }
/// <summary>
/// Gets or sets additional information. This is useful for extenders so that reads and writes don't clobber unknown fields.
/// </summary>
[YamlMember(Alias = "extensions")]
public IDictionary<string, dynamic> Extensions { get; set; }
}
}
namespace k8s.KubeConfigModels
{
using System.Collections.Generic;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
/// <summary>
/// Contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
/// </summary>
public class UserCredentials
{
/// <summary>
/// Gets or sets PEM-encoded data from a client cert file for TLS. Overrides <see cref="ClientCertificate"/>.
/// </summary>
[YamlMember(Alias = "client-certificate-data", ApplyNamingConventions = false)]
public string ClientCertificateData { get; set; }
/// <summary>
/// Gets or sets the path to a client cert file for TLS.
/// </summary>
[YamlMember(Alias = "client-certificate", ApplyNamingConventions = false)]
public string ClientCertificate { get; set; }
/// <summary>
/// Gets or sets PEM-encoded data from a client key file for TLS. Overrides <see cref="ClientKey"/>.
/// </summary>
[YamlMember(Alias = "client-key-data", ApplyNamingConventions = false)]
public string ClientKeyData { get; set; }
/// <summary>
/// Gets or sets the path to a client key file for TLS.
/// </summary>
[YamlMember(Alias = "client-key", ApplyNamingConventions = false)]
public string ClientKey { get; set; }
/// <summary>
/// Gets or sets the bearer token for authentication to the kubernetes cluster.
/// </summary>
[YamlMember(Alias = "token")]
public string Token { get; set; }
/// <summary>
/// Gets or sets the username to imperonate. The name matches the flag.
/// </summary>
[YamlMember(Alias = "as")]
public string Impersonate { get; set; }
/// <summary>
/// Gets or sets the groups to imperonate.
/// </summary>
[YamlMember(Alias = "as-groups", ApplyNamingConventions = false)]
public IEnumerable<string> ImpersonateGroups { get; set; } = new string[0];
/// <summary>
/// Gets or sets additional information for impersonated user.
/// </summary>
[YamlMember(Alias = "as-user-extra", ApplyNamingConventions = false)]
public Dictionary<string, string> ImpersonateUserExtra { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Gets or sets the username for basic authentication to the kubernetes cluster.
/// </summary>
[YamlMember(Alias = "username")]
public string UserName { get; set; }
/// <summary>
/// Gets or sets the password for basic authentication to the kubernetes cluster.
/// </summary>
[YamlMember(Alias = "password")]
public string Password { get; set; }
/// <summary>
/// Gets or sets custom authentication plugin for the kubernetes cluster.
/// </summary>
[YamlMember(Alias = "auth-provider", ApplyNamingConventions = false)]
public AuthProvider AuthProvider { get; set; }
/// <summary>
/// Gets or sets additional information. This is useful for extenders so that reads and writes don't clobber unknown fields.
/// </summary>
[YamlMember(Alias = "extensions")]
public IDictionary<string, dynamic> Extensions { get; set; }
}
}

View File

@@ -1,275 +1,275 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using k8s.Exceptions;
using k8s.Models;
using Microsoft.Rest;
namespace k8s
{
public partial class Kubernetes
{
#if MONOANDROID8_1
/// <summary>
/// Initializes a new instance of the <see cref="Kubernetes" /> class.
/// </summary>
/// <param name='config'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
/// <param name="handlers">
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
public Kubernetes(KubernetesClientConfiguration config, params DelegatingHandler[] handlers) : this(new Xamarin.Android.Net.AndroidClientHandler(), handlers)
{
if (string.IsNullOrWhiteSpace(config.Host))
{
throw new KubeConfigException("Host url must be set");
}
try
{
BaseUri = new Uri(config.Host);
}
catch (UriFormatException e)
{
throw new KubeConfigException("Bad host url", e);
}
CaCert = config.SslCaCert;
SkipTlsVerify = config.SkipTlsVerify;
if (BaseUri.Scheme == "https")
{
if (config.SkipTlsVerify)
{
System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
{
return true;
};
}
else
{
if (CaCert == null)
{
throw new KubeConfigException("a CA must be set when SkipTlsVerify === false");
}
using (System.IO.MemoryStream certStream = new System.IO.MemoryStream(config.SslCaCert.RawData))
{
Java.Security.Cert.Certificate cert = Java.Security.Cert.CertificateFactory.GetInstance("X509").GenerateCertificate(certStream);
Xamarin.Android.Net.AndroidClientHandler handler = (Xamarin.Android.Net.AndroidClientHandler)this.HttpClientHandler;
handler.TrustedCerts = new System.Collections.Generic.List<Java.Security.Cert.Certificate>()
{
cert
};
}
}
}
// set credentails for the kubernernet client
SetCredentials(config, HttpClientHandler);
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="Kubernetes" /> class.
/// </summary>
/// <param name='config'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
/// <param name="handlers">
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
public Kubernetes(KubernetesClientConfiguration config, params DelegatingHandler[] handlers) : this(handlers)
{
if (string.IsNullOrWhiteSpace(config.Host))
{
throw new KubeConfigException("Host url must be set");
}
try
{
BaseUri = new Uri(config.Host);
}
catch (UriFormatException e)
{
throw new KubeConfigException("Bad host url", e);
}
CaCert = config.SslCaCert;
SkipTlsVerify = config.SkipTlsVerify;
if (BaseUri.Scheme == "https")
{
if (config.SkipTlsVerify)
{
#if NET452
((WebRequestHandler) HttpClientHandler).ServerCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) => true;
#elif XAMARINIOS1_0
System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
{
return true;
};
#else
HttpClientHandler.ServerCertificateCustomValidationCallback =
(sender, certificate, chain, sslPolicyErrors) => true;
#endif
}
else
{
if (CaCert == null)
{
throw new KubeConfigException("a CA must be set when SkipTlsVerify === false");
}
#if NET452
((WebRequestHandler) HttpClientHandler).ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
return Kubernetes.CertificateValidationCallBack(sender, CaCert, certificate, chain, sslPolicyErrors);
};
#elif XAMARINIOS1_0
System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
{
var cert = new X509Certificate2(certificate);
return Kubernetes.CertificateValidationCallBack(sender, CaCert, cert, chain, sslPolicyErrors);
};
#else
HttpClientHandler.ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
return Kubernetes.CertificateValidationCallBack(sender, CaCert, certificate, chain, sslPolicyErrors);
};
#endif
}
}
// set credentails for the kubernernet client
SetCredentials(config, HttpClientHandler);
}
#endif
private X509Certificate2 CaCert { get; }
private bool SkipTlsVerify { get; }
partial void CustomInitialize()
{
#if NET452 || XAMARINIOS1_0 || MONOANDROID8_1
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
#endif
AppendDelegatingHandler<WatcherDelegatingHandler>();
DeserializationSettings.Converters.Add(new V1Status.V1StatusObjectViewConverter());
}
private void AppendDelegatingHandler<T>() where T : DelegatingHandler, new()
{
var cur = FirstMessageHandler as DelegatingHandler;
while (cur != null)
{
var next = cur.InnerHandler as DelegatingHandler;
if (next == null)
{
// last one
// append watcher handler between to last handler
cur.InnerHandler = new T
{
InnerHandler = cur.InnerHandler
};
break;
}
cur = next;
}
}
/// <summary>
/// Set credentials for the Client
/// </summary>
/// <param name="config">k8s client configuration</param>
/// <param name="handler">http client handler for the rest client</param>
/// <returns>Task</returns>
private void SetCredentials(KubernetesClientConfiguration config, HttpClientHandler handler)
{
// set the Credentails for token based auth
if (!string.IsNullOrWhiteSpace(config.AccessToken))
{
Credentials = new TokenCredentials(config.AccessToken);
}
else if (!string.IsNullOrWhiteSpace(config.Username) && !string.IsNullOrWhiteSpace(config.Password))
{
Credentials = new BasicAuthenticationCredentials
{
UserName = config.Username,
Password = config.Password
};
}
#if XAMARINIOS1_0 || MONOANDROID8_1
// handle.ClientCertificates is not implemented in Xamarin.
return;
#endif
if ((!string.IsNullOrWhiteSpace(config.ClientCertificateData) ||
!string.IsNullOrWhiteSpace(config.ClientCertificateFilePath)) &&
(!string.IsNullOrWhiteSpace(config.ClientCertificateKeyData) ||
!string.IsNullOrWhiteSpace(config.ClientKeyFilePath)))
{
var cert = CertUtils.GeneratePfx(config);
#if NET452
((WebRequestHandler) handler).ClientCertificates.Add(cert);
#else
handler.ClientCertificates.Add(cert);
#endif
}
}
/// <summary>
/// SSl Cert Validation Callback
/// </summary>
/// <param name="sender">sender</param>
/// <param name="certificate">client certificate</param>
/// <param name="chain">chain</param>
/// <param name="sslPolicyErrors">ssl policy errors</param>
/// <returns>true if valid cert</returns>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "Unused by design")]
public static bool CertificateValidationCallBack(
object sender,
X509Certificate2 caCert,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// If the certificate is a valid, signed certificate, return true.
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
// If there are errors in the certificate chain, look at each error to determine the cause.
if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// add all your extra certificate chain
chain.ChainPolicy.ExtraStore.Add(caCert);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using k8s.Exceptions;
using k8s.Models;
using Microsoft.Rest;
namespace k8s
{
public partial class Kubernetes
{
#if MONOANDROID8_1
/// <summary>
/// Initializes a new instance of the <see cref="Kubernetes" /> class.
/// </summary>
/// <param name='config'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
/// <param name="handlers">
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
public Kubernetes(KubernetesClientConfiguration config, params DelegatingHandler[] handlers) : this(new Xamarin.Android.Net.AndroidClientHandler(), handlers)
{
if (string.IsNullOrWhiteSpace(config.Host))
{
throw new KubeConfigException("Host url must be set");
}
try
{
BaseUri = new Uri(config.Host);
}
catch (UriFormatException e)
{
throw new KubeConfigException("Bad host url", e);
}
CaCert = config.SslCaCert;
SkipTlsVerify = config.SkipTlsVerify;
if (BaseUri.Scheme == "https")
{
if (config.SkipTlsVerify)
{
System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
{
return true;
};
}
else
{
if (CaCert == null)
{
throw new KubeConfigException("a CA must be set when SkipTlsVerify === false");
}
using (System.IO.MemoryStream certStream = new System.IO.MemoryStream(config.SslCaCert.RawData))
{
Java.Security.Cert.Certificate cert = Java.Security.Cert.CertificateFactory.GetInstance("X509").GenerateCertificate(certStream);
Xamarin.Android.Net.AndroidClientHandler handler = (Xamarin.Android.Net.AndroidClientHandler)this.HttpClientHandler;
handler.TrustedCerts = new System.Collections.Generic.List<Java.Security.Cert.Certificate>()
{
cert
};
}
}
}
// set credentails for the kubernernet client
SetCredentials(config, HttpClientHandler);
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="Kubernetes" /> class.
/// </summary>
/// <param name='config'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
/// <param name="handlers">
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
public Kubernetes(KubernetesClientConfiguration config, params DelegatingHandler[] handlers) : this(handlers)
{
if (string.IsNullOrWhiteSpace(config.Host))
{
throw new KubeConfigException("Host url must be set");
}
try
{
BaseUri = new Uri(config.Host);
}
catch (UriFormatException e)
{
throw new KubeConfigException("Bad host url", e);
}
CaCert = config.SslCaCert;
SkipTlsVerify = config.SkipTlsVerify;
if (BaseUri.Scheme == "https")
{
if (config.SkipTlsVerify)
{
#if NET452
((WebRequestHandler) HttpClientHandler).ServerCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) => true;
#elif XAMARINIOS1_0
System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
{
return true;
};
#else
HttpClientHandler.ServerCertificateCustomValidationCallback =
(sender, certificate, chain, sslPolicyErrors) => true;
#endif
}
else
{
if (CaCert == null)
{
throw new KubeConfigException("a CA must be set when SkipTlsVerify === false");
}
#if NET452
((WebRequestHandler) HttpClientHandler).ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
return Kubernetes.CertificateValidationCallBack(sender, CaCert, certificate, chain, sslPolicyErrors);
};
#elif XAMARINIOS1_0
System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
{
var cert = new X509Certificate2(certificate);
return Kubernetes.CertificateValidationCallBack(sender, CaCert, cert, chain, sslPolicyErrors);
};
#else
HttpClientHandler.ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
return Kubernetes.CertificateValidationCallBack(sender, CaCert, certificate, chain, sslPolicyErrors);
};
#endif
}
}
// set credentails for the kubernernet client
SetCredentials(config, HttpClientHandler);
}
#endif
private X509Certificate2 CaCert { get; }
private bool SkipTlsVerify { get; }
partial void CustomInitialize()
{
#if NET452 || XAMARINIOS1_0 || MONOANDROID8_1
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
#endif
AppendDelegatingHandler<WatcherDelegatingHandler>();
DeserializationSettings.Converters.Add(new V1Status.V1StatusObjectViewConverter());
}
private void AppendDelegatingHandler<T>() where T : DelegatingHandler, new()
{
var cur = FirstMessageHandler as DelegatingHandler;
while (cur != null)
{
var next = cur.InnerHandler as DelegatingHandler;
if (next == null)
{
// last one
// append watcher handler between to last handler
cur.InnerHandler = new T
{
InnerHandler = cur.InnerHandler
};
break;
}
cur = next;
}
}
/// <summary>
/// Set credentials for the Client
/// </summary>
/// <param name="config">k8s client configuration</param>
/// <param name="handler">http client handler for the rest client</param>
/// <returns>Task</returns>
private void SetCredentials(KubernetesClientConfiguration config, HttpClientHandler handler)
{
// set the Credentails for token based auth
if (!string.IsNullOrWhiteSpace(config.AccessToken))
{
Credentials = new TokenCredentials(config.AccessToken);
}
else if (!string.IsNullOrWhiteSpace(config.Username) && !string.IsNullOrWhiteSpace(config.Password))
{
Credentials = new BasicAuthenticationCredentials
{
UserName = config.Username,
Password = config.Password
};
}
#if XAMARINIOS1_0 || MONOANDROID8_1
// handle.ClientCertificates is not implemented in Xamarin.
return;
#endif
if ((!string.IsNullOrWhiteSpace(config.ClientCertificateData) ||
!string.IsNullOrWhiteSpace(config.ClientCertificateFilePath)) &&
(!string.IsNullOrWhiteSpace(config.ClientCertificateKeyData) ||
!string.IsNullOrWhiteSpace(config.ClientKeyFilePath)))
{
var cert = CertUtils.GeneratePfx(config);
#if NET452
((WebRequestHandler) handler).ClientCertificates.Add(cert);
#else
handler.ClientCertificates.Add(cert);
#endif
}
}
/// <summary>
/// SSl Cert Validation Callback
/// </summary>
/// <param name="sender">sender</param>
/// <param name="certificate">client certificate</param>
/// <param name="chain">chain</param>
/// <param name="sslPolicyErrors">ssl policy errors</param>
/// <returns>true if valid cert</returns>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "Unused by design")]
public static bool CertificateValidationCallBack(
object sender,
X509Certificate2 caCert,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// If the certificate is a valid, signed certificate, return true.
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
// If there are errors in the certificate chain, look at each error to determine the cause.
if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// add all your extra certificate chain
chain.ChainPolicy.ExtraStore.Add(caCert);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
var isValid = chain.Build((X509Certificate2)certificate);
var rootCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
isValid = isValid && rootCert.RawData.SequenceEqual(caCert.RawData);
return isValid;
}
// In all other cases, return false.
return false;
}
}
}
var rootCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
isValid = isValid && rootCert.RawData.SequenceEqual(caCert.RawData);
return isValid;
}
// In all other cases, return false.
return false;
}
}
}

View File

@@ -1,402 +1,402 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using k8s.Exceptions;
using k8s.KubeConfigModels;
namespace k8s
{
public partial class KubernetesClientConfiguration
{
/// <summary>
/// kubeconfig Default Location
/// </summary>
private static readonly string KubeConfigDefaultLocation =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), @".kube\config")
: Path.Combine(Environment.GetEnvironmentVariable("HOME"), ".kube/config");
/// <summary>
/// Gets CurrentContext
/// </summary>
public string CurrentContext { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="KubernetesClientConfiguration" /> from config file
/// </summary>
/// <param name="masterUrl">kube api server endpoint</param>
/// <param name="kubeconfigPath">Explicit file path to kubeconfig. Set to null to use the default file path</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
public static KubernetesClientConfiguration BuildConfigFromConfigFile(string kubeconfigPath = null,
string currentContext = null, string masterUrl = null, bool useRelativePaths = true)
{
return BuildConfigFromConfigFile(new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation), null,
masterUrl, useRelativePaths);
}
/// <summary>
/// </summary>
/// <param name="kubeconfig">Fileinfo of the kubeconfig, cannot be null</param>
/// <param name="currentContext">override the context in config file, set null if do not want to override</param>
/// <param name="masterUrl">override the kube api server endpoint, set null if do not want to override</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
public static KubernetesClientConfiguration BuildConfigFromConfigFile(FileInfo kubeconfig,
string currentContext = null, string masterUrl = null, bool useRelativePaths = true)
{
if (kubeconfig == null)
{
throw new NullReferenceException(nameof(kubeconfig));
}
var k8SConfig = LoadKubeConfig(kubeconfig, useRelativePaths);
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
return k8SConfiguration;
}
/// <summary>
/// </summary>
/// <param name="kubeconfig">Fileinfo of the kubeconfig, cannot be null, whitespaced or empty</param>
/// <param name="currentContext">override the context in config file, set null if do not want to override</param>
/// <param name="masterUrl">overrider kube api server endpoint, set null if do not want to override</param>
public static KubernetesClientConfiguration BuildConfigFromConfigFile(Stream kubeconfig,
string currentContext = null, string masterUrl = null)
{
if (kubeconfig == null)
{
throw new NullReferenceException(nameof(kubeconfig));
}
if (!kubeconfig.CanSeek)
{
throw new Exception("Stream don't support seeking!");
}
kubeconfig.Position = 0;
var k8SConfig = Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfig).GetAwaiter().GetResult();
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
return k8SConfiguration;
}
private static KubernetesClientConfiguration GetKubernetesClientConfiguration(string currentContext, string masterUrl, K8SConfiguration k8SConfig)
{
var k8SConfiguration = new KubernetesClientConfiguration();
currentContext = currentContext ?? k8SConfig.CurrentContext;
// only init context if context if set
if (currentContext != null)
{
k8SConfiguration.InitializeContext(k8SConfig, currentContext);
}
if (!string.IsNullOrWhiteSpace(masterUrl))
{
k8SConfiguration.Host = masterUrl;
}
if (string.IsNullOrWhiteSpace(k8SConfiguration.Host))
{
throw new KubeConfigException("Cannot infer server host url either from context or masterUrl");
}
return k8SConfiguration;
}
/// <summary>
/// Validates and Intializes Client Configuration
/// </summary>
/// <param name="k8SConfig">Kubernetes Configuration</param>
/// <param name="currentContext">Current Context</param>
private void InitializeContext(K8SConfiguration k8SConfig, string currentContext)
{
// current context
var activeContext =
k8SConfig.Contexts.FirstOrDefault(
c => c.Name.Equals(currentContext, StringComparison.OrdinalIgnoreCase));
if (activeContext == null)
{
throw new KubeConfigException($"CurrentContext: {currentContext} not found in contexts in kubeconfig");
}
CurrentContext = activeContext.Name;
// cluster
SetClusterDetails(k8SConfig, activeContext);
// user
SetUserDetails(k8SConfig, activeContext);
// namespace
Namespace = activeContext.Namespace;
}
private void SetClusterDetails(K8SConfiguration k8SConfig, Context activeContext)
{
var clusterDetails =
k8SConfig.Clusters.FirstOrDefault(c => c.Name.Equals(activeContext.ContextDetails.Cluster,
StringComparison.OrdinalIgnoreCase));
if (clusterDetails?.ClusterEndpoint == null)
{
throw new KubeConfigException($"Cluster not found for context {activeContext} in kubeconfig");
}
if (string.IsNullOrWhiteSpace(clusterDetails.ClusterEndpoint.Server))
{
throw new KubeConfigException($"Server not found for current-context {activeContext} in kubeconfig");
}
Host = clusterDetails.ClusterEndpoint.Server;
SkipTlsVerify = clusterDetails.ClusterEndpoint.SkipTlsVerify;
try
{
var uri = new Uri(Host);
if (uri.Scheme == "https")
{
// check certificate for https
if (!clusterDetails.ClusterEndpoint.SkipTlsVerify &&
string.IsNullOrWhiteSpace(clusterDetails.ClusterEndpoint.CertificateAuthorityData) &&
string.IsNullOrWhiteSpace(clusterDetails.ClusterEndpoint.CertificateAuthority))
{
throw new KubeConfigException(
$"neither certificate-authority-data nor certificate-authority not found for current-context :{activeContext} in kubeconfig");
}
if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthorityData))
{
var data = clusterDetails.ClusterEndpoint.CertificateAuthorityData;
SslCaCert = new X509Certificate2(Convert.FromBase64String(data));
}
else if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthority))
{
SslCaCert = new X509Certificate2(GetFullPath(k8SConfig, clusterDetails.ClusterEndpoint.CertificateAuthority));
}
}
}
catch (UriFormatException e)
{
throw new KubeConfigException("Bad Server host url", e);
}
}
private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext)
{
if (string.IsNullOrWhiteSpace(activeContext.ContextDetails.User))
{
return;
}
var userDetails = k8SConfig.Users.FirstOrDefault(c => c.Name.Equals(activeContext.ContextDetails.User,
StringComparison.OrdinalIgnoreCase));
if (userDetails == null)
{
throw new KubeConfigException("User not found for context {activeContext.Name} in kubeconfig");
}
if (userDetails.UserCredentials == null)
{
throw new KubeConfigException($"User credentials not found for user: {userDetails.Name} in kubeconfig");
}
var userCredentialsFound = false;
// Basic and bearer tokens are mutually exclusive
if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.Token))
{
AccessToken = userDetails.UserCredentials.Token;
userCredentialsFound = true;
}
else if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.UserName) &&
!string.IsNullOrWhiteSpace(userDetails.UserCredentials.Password))
{
Username = userDetails.UserCredentials.UserName;
Password = userDetails.UserCredentials.Password;
userCredentialsFound = true;
}
// Token and cert based auth can co-exist
if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientCertificateData) &&
!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientKeyData))
{
ClientCertificateData = userDetails.UserCredentials.ClientCertificateData;
ClientCertificateKeyData = userDetails.UserCredentials.ClientKeyData;
userCredentialsFound = true;
}
if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientCertificate) &&
!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientKey))
{
ClientCertificateFilePath = GetFullPath(k8SConfig, userDetails.UserCredentials.ClientCertificate);
ClientKeyFilePath = GetFullPath(k8SConfig, userDetails.UserCredentials.ClientKey);
userCredentialsFound = true;
}
if (userDetails.UserCredentials.AuthProvider != null)
{
if (userDetails.UserCredentials.AuthProvider.Name == "azure" &&
userDetails.UserCredentials.AuthProvider.Config != null &&
userDetails.UserCredentials.AuthProvider.Config.ContainsKey("access-token"))
{
var config = userDetails.UserCredentials.AuthProvider.Config;
if (config.ContainsKey("expires-on"))
{
var expiresOn = Int32.Parse(config["expires-on"]);
DateTimeOffset expires;
#if NET452
var epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
expires = epoch.AddSeconds(expiresOn);
#else
expires = DateTimeOffset.FromUnixTimeSeconds(expiresOn);
#endif
if (DateTimeOffset.Compare(expires, DateTimeOffset.Now) <= 0)
{
var tenantId = config["tenant-id"];
var clientId = config["client-id"];
var apiServerId = config["apiserver-id"];
var refresh = config["refresh-token"];
var newToken = RenewAzureToken(tenantId, clientId, apiServerId, refresh);
config["access-token"] = newToken;
}
}
AccessToken = config["access-token"];
userCredentialsFound = true;
}
}
if (!userCredentialsFound)
{
throw new KubeConfigException(
$"User: {userDetails.Name} does not have appropriate auth credentials in kubeconfig");
}
}
public static string RenewAzureToken(string tenantId, string clientId, string apiServerId, string refresh)
{
throw new KubeConfigException("Refresh not supported.");
}
/// <summary>
/// Loads entire Kube Config from default or explicit file path
/// </summary>
/// <param name="kubeconfigPath">Explicit file path to kubeconfig. Set to null to use the default file path</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static async Task<K8SConfiguration> LoadKubeConfigAsync(string kubeconfigPath = null, bool useRelativePaths = true)
{
var fileInfo = new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation);
return await LoadKubeConfigAsync(fileInfo, useRelativePaths);
}
/// <summary>
/// Loads entire Kube Config from default or explicit file path
/// </summary>
/// <param name="kubeconfigPath">Explicit file path to kubeconfig. Set to null to use the default file path</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static K8SConfiguration LoadKubeConfig(string kubeconfigPath = null, bool useRelativePaths = true)
{
return LoadKubeConfigAsync(kubeconfigPath, useRelativePaths).GetAwaiter().GetResult();
}
// <summary>
/// Loads Kube Config
/// </summary>
/// <param name="kubeconfig">Kube config file contents</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static async Task<K8SConfiguration> LoadKubeConfigAsync(FileInfo kubeconfig, bool useRelativePaths = true)
{
if (!kubeconfig.Exists)
{
throw new KubeConfigException($"kubeconfig file not found at {kubeconfig.FullName}");
}
using (var stream = kubeconfig.OpenRead())
{
var config = await Yaml.LoadFromStreamAsync<K8SConfiguration>(stream);
if (useRelativePaths)
{
config.FileName = kubeconfig.FullName;
}
return config;
}
}
/// <summary>
/// Loads Kube Config
/// </summary>
/// <param name="kubeconfig">Kube config file contents</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static K8SConfiguration LoadKubeConfig(FileInfo kubeconfig, bool useRelativePaths = true)
{
return LoadKubeConfigAsync(kubeconfig, useRelativePaths).GetAwaiter().GetResult();
}
// <summary>
/// Loads Kube Config
/// </summary>
/// <param name="kubeconfigStream">Kube config file contents stream</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static async Task<K8SConfiguration> LoadKubeConfigAsync(Stream kubeconfigStream)
{
return await Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfigStream);
}
/// <summary>
/// Loads Kube Config
/// </summary>
/// <param name="kubeconfig">Kube config file contents stream</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static K8SConfiguration LoadKubeConfig(Stream kubeconfigStream)
{
return LoadKubeConfigAsync(kubeconfigStream).GetAwaiter().GetResult();
}
/// <summary>
/// Tries to get the full path to a file referenced from the Kubernetes configuration.
/// </summary>
/// <param name="configuration">
/// The Kubernetes configuration.
/// </param>
/// <param name="path">
/// The path to resolve.
/// </param>
/// <returns>
/// When possible a fully qualified path to the file.
/// </returns>
/// <remarks>
/// For example, if the configuration file is at "C:\Users\me\kube.config" and path is "ca.crt",
/// this will return "C:\Users\me\ca.crt". Similarly, if path is "D:\ca.cart", this will return
/// "D:\ca.crt".
/// </remarks>
private static string GetFullPath(K8SConfiguration configuration, string path)
{
// If we don't have a file name,
if (string.IsNullOrWhiteSpace(configuration.FileName) || Path.IsPathRooted(path))
{
return path;
}
else
{
return Path.Combine(Path.GetDirectoryName(configuration.FileName), path);
}
}
}
}
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using k8s.Exceptions;
using k8s.KubeConfigModels;
namespace k8s
{
public partial class KubernetesClientConfiguration
{
/// <summary>
/// kubeconfig Default Location
/// </summary>
private static readonly string KubeConfigDefaultLocation =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), @".kube\config")
: Path.Combine(Environment.GetEnvironmentVariable("HOME"), ".kube/config");
/// <summary>
/// Gets CurrentContext
/// </summary>
public string CurrentContext { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="KubernetesClientConfiguration" /> from config file
/// </summary>
/// <param name="masterUrl">kube api server endpoint</param>
/// <param name="kubeconfigPath">Explicit file path to kubeconfig. Set to null to use the default file path</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
public static KubernetesClientConfiguration BuildConfigFromConfigFile(string kubeconfigPath = null,
string currentContext = null, string masterUrl = null, bool useRelativePaths = true)
{
return BuildConfigFromConfigFile(new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation), null,
masterUrl, useRelativePaths);
}
/// <summary>
/// </summary>
/// <param name="kubeconfig">Fileinfo of the kubeconfig, cannot be null</param>
/// <param name="currentContext">override the context in config file, set null if do not want to override</param>
/// <param name="masterUrl">override the kube api server endpoint, set null if do not want to override</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
public static KubernetesClientConfiguration BuildConfigFromConfigFile(FileInfo kubeconfig,
string currentContext = null, string masterUrl = null, bool useRelativePaths = true)
{
if (kubeconfig == null)
{
throw new NullReferenceException(nameof(kubeconfig));
}
var k8SConfig = LoadKubeConfig(kubeconfig, useRelativePaths);
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
return k8SConfiguration;
}
/// <summary>
/// </summary>
/// <param name="kubeconfig">Fileinfo of the kubeconfig, cannot be null, whitespaced or empty</param>
/// <param name="currentContext">override the context in config file, set null if do not want to override</param>
/// <param name="masterUrl">overrider kube api server endpoint, set null if do not want to override</param>
public static KubernetesClientConfiguration BuildConfigFromConfigFile(Stream kubeconfig,
string currentContext = null, string masterUrl = null)
{
if (kubeconfig == null)
{
throw new NullReferenceException(nameof(kubeconfig));
}
if (!kubeconfig.CanSeek)
{
throw new Exception("Stream don't support seeking!");
}
kubeconfig.Position = 0;
var k8SConfig = Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfig).GetAwaiter().GetResult();
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
return k8SConfiguration;
}
private static KubernetesClientConfiguration GetKubernetesClientConfiguration(string currentContext, string masterUrl, K8SConfiguration k8SConfig)
{
var k8SConfiguration = new KubernetesClientConfiguration();
currentContext = currentContext ?? k8SConfig.CurrentContext;
// only init context if context if set
if (currentContext != null)
{
k8SConfiguration.InitializeContext(k8SConfig, currentContext);
}
if (!string.IsNullOrWhiteSpace(masterUrl))
{
k8SConfiguration.Host = masterUrl;
}
if (string.IsNullOrWhiteSpace(k8SConfiguration.Host))
{
throw new KubeConfigException("Cannot infer server host url either from context or masterUrl");
}
return k8SConfiguration;
}
/// <summary>
/// Validates and Intializes Client Configuration
/// </summary>
/// <param name="k8SConfig">Kubernetes Configuration</param>
/// <param name="currentContext">Current Context</param>
private void InitializeContext(K8SConfiguration k8SConfig, string currentContext)
{
// current context
var activeContext =
k8SConfig.Contexts.FirstOrDefault(
c => c.Name.Equals(currentContext, StringComparison.OrdinalIgnoreCase));
if (activeContext == null)
{
throw new KubeConfigException($"CurrentContext: {currentContext} not found in contexts in kubeconfig");
}
CurrentContext = activeContext.Name;
// cluster
SetClusterDetails(k8SConfig, activeContext);
// user
SetUserDetails(k8SConfig, activeContext);
// namespace
Namespace = activeContext.Namespace;
}
private void SetClusterDetails(K8SConfiguration k8SConfig, Context activeContext)
{
var clusterDetails =
k8SConfig.Clusters.FirstOrDefault(c => c.Name.Equals(activeContext.ContextDetails.Cluster,
StringComparison.OrdinalIgnoreCase));
if (clusterDetails?.ClusterEndpoint == null)
{
throw new KubeConfigException($"Cluster not found for context {activeContext} in kubeconfig");
}
if (string.IsNullOrWhiteSpace(clusterDetails.ClusterEndpoint.Server))
{
throw new KubeConfigException($"Server not found for current-context {activeContext} in kubeconfig");
}
Host = clusterDetails.ClusterEndpoint.Server;
SkipTlsVerify = clusterDetails.ClusterEndpoint.SkipTlsVerify;
try
{
var uri = new Uri(Host);
if (uri.Scheme == "https")
{
// check certificate for https
if (!clusterDetails.ClusterEndpoint.SkipTlsVerify &&
string.IsNullOrWhiteSpace(clusterDetails.ClusterEndpoint.CertificateAuthorityData) &&
string.IsNullOrWhiteSpace(clusterDetails.ClusterEndpoint.CertificateAuthority))
{
throw new KubeConfigException(
$"neither certificate-authority-data nor certificate-authority not found for current-context :{activeContext} in kubeconfig");
}
if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthorityData))
{
var data = clusterDetails.ClusterEndpoint.CertificateAuthorityData;
SslCaCert = new X509Certificate2(Convert.FromBase64String(data));
}
else if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthority))
{
SslCaCert = new X509Certificate2(GetFullPath(k8SConfig, clusterDetails.ClusterEndpoint.CertificateAuthority));
}
}
}
catch (UriFormatException e)
{
throw new KubeConfigException("Bad Server host url", e);
}
}
private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext)
{
if (string.IsNullOrWhiteSpace(activeContext.ContextDetails.User))
{
return;
}
var userDetails = k8SConfig.Users.FirstOrDefault(c => c.Name.Equals(activeContext.ContextDetails.User,
StringComparison.OrdinalIgnoreCase));
if (userDetails == null)
{
throw new KubeConfigException("User not found for context {activeContext.Name} in kubeconfig");
}
if (userDetails.UserCredentials == null)
{
throw new KubeConfigException($"User credentials not found for user: {userDetails.Name} in kubeconfig");
}
var userCredentialsFound = false;
// Basic and bearer tokens are mutually exclusive
if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.Token))
{
AccessToken = userDetails.UserCredentials.Token;
userCredentialsFound = true;
}
else if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.UserName) &&
!string.IsNullOrWhiteSpace(userDetails.UserCredentials.Password))
{
Username = userDetails.UserCredentials.UserName;
Password = userDetails.UserCredentials.Password;
userCredentialsFound = true;
}
// Token and cert based auth can co-exist
if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientCertificateData) &&
!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientKeyData))
{
ClientCertificateData = userDetails.UserCredentials.ClientCertificateData;
ClientCertificateKeyData = userDetails.UserCredentials.ClientKeyData;
userCredentialsFound = true;
}
if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientCertificate) &&
!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientKey))
{
ClientCertificateFilePath = GetFullPath(k8SConfig, userDetails.UserCredentials.ClientCertificate);
ClientKeyFilePath = GetFullPath(k8SConfig, userDetails.UserCredentials.ClientKey);
userCredentialsFound = true;
}
if (userDetails.UserCredentials.AuthProvider != null)
{
if (userDetails.UserCredentials.AuthProvider.Name == "azure" &&
userDetails.UserCredentials.AuthProvider.Config != null &&
userDetails.UserCredentials.AuthProvider.Config.ContainsKey("access-token"))
{
var config = userDetails.UserCredentials.AuthProvider.Config;
if (config.ContainsKey("expires-on"))
{
var expiresOn = Int32.Parse(config["expires-on"]);
DateTimeOffset expires;
#if NET452
var epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
expires = epoch.AddSeconds(expiresOn);
#else
expires = DateTimeOffset.FromUnixTimeSeconds(expiresOn);
#endif
if (DateTimeOffset.Compare(expires, DateTimeOffset.Now) <= 0)
{
var tenantId = config["tenant-id"];
var clientId = config["client-id"];
var apiServerId = config["apiserver-id"];
var refresh = config["refresh-token"];
var newToken = RenewAzureToken(tenantId, clientId, apiServerId, refresh);
config["access-token"] = newToken;
}
}
AccessToken = config["access-token"];
userCredentialsFound = true;
}
}
if (!userCredentialsFound)
{
throw new KubeConfigException(
$"User: {userDetails.Name} does not have appropriate auth credentials in kubeconfig");
}
}
public static string RenewAzureToken(string tenantId, string clientId, string apiServerId, string refresh)
{
throw new KubeConfigException("Refresh not supported.");
}
/// <summary>
/// Loads entire Kube Config from default or explicit file path
/// </summary>
/// <param name="kubeconfigPath">Explicit file path to kubeconfig. Set to null to use the default file path</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static async Task<K8SConfiguration> LoadKubeConfigAsync(string kubeconfigPath = null, bool useRelativePaths = true)
{
var fileInfo = new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation);
return await LoadKubeConfigAsync(fileInfo, useRelativePaths);
}
/// <summary>
/// Loads entire Kube Config from default or explicit file path
/// </summary>
/// <param name="kubeconfigPath">Explicit file path to kubeconfig. Set to null to use the default file path</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static K8SConfiguration LoadKubeConfig(string kubeconfigPath = null, bool useRelativePaths = true)
{
return LoadKubeConfigAsync(kubeconfigPath, useRelativePaths).GetAwaiter().GetResult();
}
// <summary>
/// Loads Kube Config
/// </summary>
/// <param name="kubeconfig">Kube config file contents</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static async Task<K8SConfiguration> LoadKubeConfigAsync(FileInfo kubeconfig, bool useRelativePaths = true)
{
if (!kubeconfig.Exists)
{
throw new KubeConfigException($"kubeconfig file not found at {kubeconfig.FullName}");
}
using (var stream = kubeconfig.OpenRead())
{
var config = await Yaml.LoadFromStreamAsync<K8SConfiguration>(stream);
if (useRelativePaths)
{
config.FileName = kubeconfig.FullName;
}
return config;
}
}
/// <summary>
/// Loads Kube Config
/// </summary>
/// <param name="kubeconfig">Kube config file contents</param>
/// <param name="useRelativePaths">When <see langword="true"/>, the paths in the kubeconfig file will be considered to be relative to the directory in which the kubeconfig
/// file is located. When <see langword="false"/>, the paths will be considered to be relative to the current working directory.</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static K8SConfiguration LoadKubeConfig(FileInfo kubeconfig, bool useRelativePaths = true)
{
return LoadKubeConfigAsync(kubeconfig, useRelativePaths).GetAwaiter().GetResult();
}
// <summary>
/// Loads Kube Config
/// </summary>
/// <param name="kubeconfigStream">Kube config file contents stream</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static async Task<K8SConfiguration> LoadKubeConfigAsync(Stream kubeconfigStream)
{
return await Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfigStream);
}
/// <summary>
/// Loads Kube Config
/// </summary>
/// <param name="kubeconfig">Kube config file contents stream</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static K8SConfiguration LoadKubeConfig(Stream kubeconfigStream)
{
return LoadKubeConfigAsync(kubeconfigStream).GetAwaiter().GetResult();
}
/// <summary>
/// Tries to get the full path to a file referenced from the Kubernetes configuration.
/// </summary>
/// <param name="configuration">
/// The Kubernetes configuration.
/// </param>
/// <param name="path">
/// The path to resolve.
/// </param>
/// <returns>
/// When possible a fully qualified path to the file.
/// </returns>
/// <remarks>
/// For example, if the configuration file is at "C:\Users\me\kube.config" and path is "ca.crt",
/// this will return "C:\Users\me\ca.crt". Similarly, if path is "D:\ca.cart", this will return
/// "D:\ca.crt".
/// </remarks>
private static string GetFullPath(K8SConfiguration configuration, string path)
{
// If we don't have a file name,
if (string.IsNullOrWhiteSpace(configuration.FileName) || Path.IsPathRooted(path))
{
return path;
}
else
{
return Path.Combine(Path.GetDirectoryName(configuration.FileName), path);
}
}
}
}

View File

@@ -1,35 +1,35 @@
using System;
using System.IO;
using k8s.Exceptions;
namespace k8s
{
public partial class KubernetesClientConfiguration
{
private const string ServiceaccountPath = "/var/run/secrets/kubernetes.io/serviceaccount/";
private const string ServiceAccountTokenKeyFileName = "token";
private const string ServiceAccountRootCAKeyFileName = "ca.crt";
public static KubernetesClientConfiguration InClusterConfig()
{
var host = Environment.GetEnvironmentVariable("KUBERNETES_SERVICE_HOST");
var port = Environment.GetEnvironmentVariable("KUBERNETES_SERVICE_PORT");
if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(port))
{
throw new KubeConfigException(
"unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined");
}
var token = File.ReadAllText(Path.Combine(ServiceaccountPath, ServiceAccountTokenKeyFileName));
var rootCAFile = Path.Combine(ServiceaccountPath, ServiceAccountRootCAKeyFileName);
return new KubernetesClientConfiguration
{
Host = new UriBuilder("https", host, Convert.ToInt32(port)).ToString(),
AccessToken = token,
SslCaCert = CertUtils.LoadPemFileCert(rootCAFile)
};
}
}
using System;
using System.IO;
using k8s.Exceptions;
namespace k8s
{
public partial class KubernetesClientConfiguration
{
private const string ServiceaccountPath = "/var/run/secrets/kubernetes.io/serviceaccount/";
private const string ServiceAccountTokenKeyFileName = "token";
private const string ServiceAccountRootCAKeyFileName = "ca.crt";
public static KubernetesClientConfiguration InClusterConfig()
{
var host = Environment.GetEnvironmentVariable("KUBERNETES_SERVICE_HOST");
var port = Environment.GetEnvironmentVariable("KUBERNETES_SERVICE_PORT");
if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(port))
{
throw new KubeConfigException(
"unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined");
}
var token = File.ReadAllText(Path.Combine(ServiceaccountPath, ServiceAccountTokenKeyFileName));
var rootCAFile = Path.Combine(ServiceaccountPath, ServiceAccountRootCAKeyFileName);
return new KubernetesClientConfiguration
{
Host = new UriBuilder("https", host, Convert.ToInt32(port)).ToString(),
AccessToken = token,
SslCaCert = CertUtils.LoadPemFileCert(rootCAFile)
};
}
}
}

View File

@@ -1,74 +1,74 @@
using System.Security.Cryptography.X509Certificates;
namespace k8s
{
/// <summary>
/// Represents a set of kubernetes client configuration settings
/// </summary>
public partial class KubernetesClientConfiguration
{
/// <summary>
/// Gets current namespace
/// </summary>
public string Namespace { get; set; }
/// <summary>
/// Gets Host
/// </summary>
public string Host { get; set; }
/// <summary>
/// Gets SslCaCert
/// </summary>
public X509Certificate2 SslCaCert { get; set; }
/// <summary>
/// Gets ClientCertificateData
/// </summary>
public string ClientCertificateData { get; set; }
/// <summary>
/// Gets ClientCertificate Key
/// </summary>
public string ClientCertificateKeyData { get; set; }
/// <summary>
/// Gets ClientCertificate filename
/// </summary>
public string ClientCertificateFilePath { get; set; }
/// <summary>
/// Gets ClientCertificate Key filename
/// </summary>
public string ClientKeyFilePath { get; set; }
/// <summary>
/// Gets a value indicating whether to skip ssl server cert validation
/// </summary>
public bool SkipTlsVerify { get; set; }
/// <summary>
/// Gets or sets the HTTP user agent.
/// </summary>
/// <value>Http user agent.</value>
public string UserAgent { get; set; }
/// <summary>
/// Gets or sets the username (HTTP basic authentication).
/// </summary>
/// <value>The username.</value>
public string Username { get; set; }
/// <summary>
/// Gets or sets the password (HTTP basic authentication).
/// </summary>
/// <value>The password.</value>
public string Password { get; set; }
/// <summary>
/// Gets or sets the access token for OAuth2 authentication.
/// </summary>
/// <value>The access token.</value>
public string AccessToken { get; set; }
}
}
using System.Security.Cryptography.X509Certificates;
namespace k8s
{
/// <summary>
/// Represents a set of kubernetes client configuration settings
/// </summary>
public partial class KubernetesClientConfiguration
{
/// <summary>
/// Gets current namespace
/// </summary>
public string Namespace { get; set; }
/// <summary>
/// Gets Host
/// </summary>
public string Host { get; set; }
/// <summary>
/// Gets SslCaCert
/// </summary>
public X509Certificate2 SslCaCert { get; set; }
/// <summary>
/// Gets ClientCertificateData
/// </summary>
public string ClientCertificateData { get; set; }
/// <summary>
/// Gets ClientCertificate Key
/// </summary>
public string ClientCertificateKeyData { get; set; }
/// <summary>
/// Gets ClientCertificate filename
/// </summary>
public string ClientCertificateFilePath { get; set; }
/// <summary>
/// Gets ClientCertificate Key filename
/// </summary>
public string ClientKeyFilePath { get; set; }
/// <summary>
/// Gets a value indicating whether to skip ssl server cert validation
/// </summary>
public bool SkipTlsVerify { get; set; }
/// <summary>
/// Gets or sets the HTTP user agent.
/// </summary>
/// <value>Http user agent.</value>
public string UserAgent { get; set; }
/// <summary>
/// Gets or sets the username (HTTP basic authentication).
/// </summary>
/// <value>The username.</value>
public string Username { get; set; }
/// <summary>
/// Gets or sets the password (HTTP basic authentication).
/// </summary>
/// <value>The password.</value>
public string Password { get; set; }
/// <summary>
/// Gets or sets the access token for OAuth2 authentication.
/// </summary>
/// <value>The access token.</value>
public string AccessToken { get; set; }
}
}

View File

@@ -9,7 +9,7 @@ namespace k8s
/// You can use the <see cref="KubernetesObject"/> if you receive JSON from a Kubernetes API server but
/// are unsure which object the API server is about to return. You can parse the JSON as a <see cref="KubernetesObject"/>
/// and use the <see cref="ApiVersion"/> and <see cref="Kind"/> properties to get basic metadata about any Kubernetes object.
/// You can then
/// You can then
/// </remarks>
public class KubernetesObject : IKubernetesObject
{

View File

@@ -1,183 +1,183 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Fractions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Fractions;
using Newtonsoft.Json;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace k8s.Models
{
internal class QuantityConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var q = (ResourceQuantity)value;
if (q != null)
{
serializer.Serialize(writer, q.ToString());
return;
}
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
return new ResourceQuantity(serializer.Deserialize<string>(reader));
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
}
/// <summary>
/// port https://github.com/kubernetes/apimachinery/blob/master/pkg/api/resource/quantity.go to c#
/// Quantity is a fixed-point representation of a number.
/// It provides convenient marshaling/unmarshaling in JSON and YAML,
/// in addition to String() and Int64() accessors.
/// The serialization format is:
/// quantity ::= signedNumber suffix
/// (Note that suffix may be empty, from the "" case in decimalSI.)
/// digit ::= 0 | 1 | ... | 9
/// digits ::= digit | digitdigits
/// number ::= digits | digits.digits | digits. | .digits
/// sign ::= "+" | "-"
/// signedNumber ::= number | signnumber
/// suffix ::= binarySI | decimalExponent | decimalSI
/// binarySI ::= Ki | Mi | Gi | Ti | Pi | Ei
/// (International System of units; See: http:///physics.nist.gov/cuu/Units/binary.html)
/// decimalSI ::= m | "" | k | M | G | T | P | E
/// (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)
/// decimalExponent ::= "e" signedNumber | "E" signedNumber
/// No matter which of the three exponent forms is used, no quantity may represent
/// a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal
/// places. Numbers larger or more precise will be capped or rounded up.
/// (E.g.: 0.1m will rounded up to 1m.)
/// This may be extended in the future if we require larger or smaller quantities.
/// When a Quantity is parsed from a string, it will remember the type of suffix
/// it had, and will use the same type again when it is serialized.
/// Before serializing, Quantity will be put in "canonical form".
/// This means that Exponent/suffix will be adjusted up or down (with a
/// corresponding increase or decrease in Mantissa) such that:
/// a. No precision is lost
/// b. No fractional digits will be emitted
/// c. The exponent (or suffix) is as large as possible.
/// The sign will be omitted unless the number is negative.
/// Examples:
/// 1.5 will be serialized as "1500m"
/// 1.5Gi will be serialized as "1536Mi"
/// NOTE: We reserve the right to amend this canonical format, perhaps to
/// allow 1.5 to be canonical.
/// TODO: Remove above disclaimer after all bikeshedding about format is over,
/// or after March 2015.
/// Note that the quantity will NEVER be internally represented by a
/// floating point number. That is the whole point of this exercise.
/// Non-canonical values will still parse as long as they are well formed,
/// but will be re-emitted in their canonical form. (So always use canonical
/// form, or don't diff.)
/// This format is intended to make it difficult to use these numbers without
/// writing some sort of special handling code in the hopes that that will
/// cause implementors to also use a fixed point implementation.
/// </summary>
namespace k8s.Models
{
internal class QuantityConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var q = (ResourceQuantity)value;
if (q != null)
{
serializer.Serialize(writer, q.ToString());
return;
}
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
return new ResourceQuantity(serializer.Deserialize<string>(reader));
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
}
/// <summary>
/// port https://github.com/kubernetes/apimachinery/blob/master/pkg/api/resource/quantity.go to c#
/// Quantity is a fixed-point representation of a number.
/// It provides convenient marshaling/unmarshaling in JSON and YAML,
/// in addition to String() and Int64() accessors.
/// The serialization format is:
/// quantity ::= signedNumber suffix
/// (Note that suffix may be empty, from the "" case in decimalSI.)
/// digit ::= 0 | 1 | ... | 9
/// digits ::= digit | digitdigits
/// number ::= digits | digits.digits | digits. | .digits
/// sign ::= "+" | "-"
/// signedNumber ::= number | signnumber
/// suffix ::= binarySI | decimalExponent | decimalSI
/// binarySI ::= Ki | Mi | Gi | Ti | Pi | Ei
/// (International System of units; See: http:///physics.nist.gov/cuu/Units/binary.html)
/// decimalSI ::= m | "" | k | M | G | T | P | E
/// (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)
/// decimalExponent ::= "e" signedNumber | "E" signedNumber
/// No matter which of the three exponent forms is used, no quantity may represent
/// a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal
/// places. Numbers larger or more precise will be capped or rounded up.
/// (E.g.: 0.1m will rounded up to 1m.)
/// This may be extended in the future if we require larger or smaller quantities.
/// When a Quantity is parsed from a string, it will remember the type of suffix
/// it had, and will use the same type again when it is serialized.
/// Before serializing, Quantity will be put in "canonical form".
/// This means that Exponent/suffix will be adjusted up or down (with a
/// corresponding increase or decrease in Mantissa) such that:
/// a. No precision is lost
/// b. No fractional digits will be emitted
/// c. The exponent (or suffix) is as large as possible.
/// The sign will be omitted unless the number is negative.
/// Examples:
/// 1.5 will be serialized as "1500m"
/// 1.5Gi will be serialized as "1536Mi"
/// NOTE: We reserve the right to amend this canonical format, perhaps to
/// allow 1.5 to be canonical.
/// TODO: Remove above disclaimer after all bikeshedding about format is over,
/// or after March 2015.
/// Note that the quantity will NEVER be internally represented by a
/// floating point number. That is the whole point of this exercise.
/// Non-canonical values will still parse as long as they are well formed,
/// but will be re-emitted in their canonical form. (So always use canonical
/// form, or don't diff.)
/// This format is intended to make it difficult to use these numbers without
/// writing some sort of special handling code in the hopes that that will
/// cause implementors to also use a fixed point implementation.
/// </summary>
[JsonConverter(typeof(QuantityConverter))]
public partial class ResourceQuantity : IYamlConvertible
{
public enum SuffixFormat
{
DecimalExponent,
BinarySI,
DecimalSI
}
public static readonly decimal MaxAllowed = (decimal)BigInteger.Pow(2, 63) - 1;
private static readonly char[] SuffixChars = "eEinumkKMGTP".ToCharArray();
private Fraction _unitlessValue;
public ResourceQuantity(decimal n, int exp, SuffixFormat format)
{
_unitlessValue = Fraction.FromDecimal(n) * Fraction.Pow(10, exp);
Format = format;
}
public SuffixFormat Format { get; private set; }
public string CanonicalizeString()
{
return CanonicalizeString(Format);
}
public override string ToString()
{
return CanonicalizeString();
}
protected bool Equals(ResourceQuantity other)
{
return Format == other.Format && _unitlessValue.Equals(other._unitlessValue);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((ResourceQuantity) obj);
}
public override int GetHashCode()
{
unchecked
{
return ((int) Format * 397) ^ _unitlessValue.GetHashCode();
}
}
//
// CanonicalizeString = go version CanonicalizeBytes
// CanonicalizeBytes returns the canonical form of q and its suffix (see comment on Quantity).
//
// Note about BinarySI:
// * If q.Format is set to BinarySI and q.Amount represents a non-zero value between
// -1 and +1, it will be emitted as if q.Format were DecimalSI.
// * Otherwise, if q.Format is set to BinarySI, fractional parts of q.Amount will be
// rounded up. (1.1i becomes 2i.)
public string CanonicalizeString(SuffixFormat suffixFormat)
{
if (suffixFormat == SuffixFormat.BinarySI)
{
if (-1024 < _unitlessValue && _unitlessValue < 1024)
{
return Suffixer.AppendMaxSuffix(_unitlessValue, SuffixFormat.DecimalSI);
}
if (HasMantissa(_unitlessValue))
{
return Suffixer.AppendMaxSuffix(_unitlessValue, SuffixFormat.DecimalSI);
}
}
return Suffixer.AppendMaxSuffix(_unitlessValue, suffixFormat);
}
// ctor
partial void CustomInit()
public partial class ResourceQuantity : IYamlConvertible
{
public enum SuffixFormat
{
DecimalExponent,
BinarySI,
DecimalSI
}
public static readonly decimal MaxAllowed = (decimal)BigInteger.Pow(2, 63) - 1;
private static readonly char[] SuffixChars = "eEinumkKMGTP".ToCharArray();
private Fraction _unitlessValue;
public ResourceQuantity(decimal n, int exp, SuffixFormat format)
{
_unitlessValue = Fraction.FromDecimal(n) * Fraction.Pow(10, exp);
Format = format;
}
public SuffixFormat Format { get; private set; }
public string CanonicalizeString()
{
return CanonicalizeString(Format);
}
public override string ToString()
{
return CanonicalizeString();
}
protected bool Equals(ResourceQuantity other)
{
return Format == other.Format && _unitlessValue.Equals(other._unitlessValue);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((ResourceQuantity) obj);
}
public override int GetHashCode()
{
unchecked
{
return ((int) Format * 397) ^ _unitlessValue.GetHashCode();
}
}
//
// CanonicalizeString = go version CanonicalizeBytes
// CanonicalizeBytes returns the canonical form of q and its suffix (see comment on Quantity).
//
// Note about BinarySI:
// * If q.Format is set to BinarySI and q.Amount represents a non-zero value between
// -1 and +1, it will be emitted as if q.Format were DecimalSI.
// * Otherwise, if q.Format is set to BinarySI, fractional parts of q.Amount will be
// rounded up. (1.1i becomes 2i.)
public string CanonicalizeString(SuffixFormat suffixFormat)
{
if (suffixFormat == SuffixFormat.BinarySI)
{
if (-1024 < _unitlessValue && _unitlessValue < 1024)
{
return Suffixer.AppendMaxSuffix(_unitlessValue, SuffixFormat.DecimalSI);
}
if (HasMantissa(_unitlessValue))
{
return Suffixer.AppendMaxSuffix(_unitlessValue, SuffixFormat.DecimalSI);
}
}
return Suffixer.AppendMaxSuffix(_unitlessValue, suffixFormat);
}
// ctor
partial void CustomInit()
{
if (Value == null)
{
@@ -186,35 +186,35 @@ namespace k8s.Models
Format = SuffixFormat.BinarySI;
return;
}
var value = Value.Trim();
var si = value.IndexOfAny(SuffixChars);
if (si == -1)
{
si = value.Length;
}
var literal = Fraction.FromString(value.Substring(0, si));
var suffixer = new Suffixer(value.Substring(si));
_unitlessValue = literal.Multiply(Fraction.Pow(suffixer.Base, suffixer.Exponent));
Format = suffixer.Format;
if (Format == SuffixFormat.BinarySI && _unitlessValue > Fraction.FromDecimal(MaxAllowed))
{
_unitlessValue = Fraction.FromDecimal(MaxAllowed);
}
}
private static bool HasMantissa(Fraction value)
{
if (value.IsZero)
{
return false;
}
return BigInteger.Remainder(value.Numerator, value.Denominator) > 0;
var value = Value.Trim();
var si = value.IndexOfAny(SuffixChars);
if (si == -1)
{
si = value.Length;
}
var literal = Fraction.FromString(value.Substring(0, si));
var suffixer = new Suffixer(value.Substring(si));
_unitlessValue = literal.Multiply(Fraction.Pow(suffixer.Base, suffixer.Exponent));
Format = suffixer.Format;
if (Format == SuffixFormat.BinarySI && _unitlessValue > Fraction.FromDecimal(MaxAllowed))
{
_unitlessValue = Fraction.FromDecimal(MaxAllowed);
}
}
private static bool HasMantissa(Fraction value)
{
if (value.IsZero)
{
return false;
}
return BigInteger.Remainder(value.Numerator, value.Denominator) > 0;
}
/// <inheritdoc/>
@@ -239,98 +239,98 @@ namespace k8s.Models
emitter.Emit(new Scalar(this.ToString()));
}
public static implicit operator decimal(ResourceQuantity v)
{
return v._unitlessValue.ToDecimal();
}
public static implicit operator ResourceQuantity(decimal v)
{
return new ResourceQuantity(v, 0, SuffixFormat.DecimalExponent);
}
#region suffixer
private class Suffixer
{
private static readonly IReadOnlyDictionary<string, (int, int)> BinSuffixes =
new Dictionary<string, (int, int)>
{
// Don't emit an error when trying to produce
// a suffix for 2^0.
{"", (2, 0)},
{"Ki", (2, 10)},
{"Mi", (2, 20)},
{"Gi", (2, 30)},
{"Ti", (2, 40)},
{"Pi", (2, 50)},
{"Ei", (2, 60)}
};
private static readonly IReadOnlyDictionary<string, (int, int)> DecSuffixes =
new Dictionary<string, (int, int)>
{
{"n", (10, -9)},
{"u", (10, -6)},
{"m", (10, -3)},
{"", (10, 0)},
{"k", (10, 3)},
{"M", (10, 6)},
{"G", (10, 9)},
{"T", (10, 12)},
{"P", (10, 15)},
{"E", (10, 18)}
};
public Suffixer(string suffix)
{
// looked up
{
if (DecSuffixes.TryGetValue(suffix, out var be))
{
(Base, Exponent) = be;
Format = SuffixFormat.DecimalSI;
return;
}
}
{
if (BinSuffixes.TryGetValue(suffix, out var be))
{
(Base, Exponent) = be;
Format = SuffixFormat.BinarySI;
return;
}
}
if (char.ToLower(suffix[0]) == 'e')
{
Base = 10;
Exponent = int.Parse(suffix.Substring(1));
Format = SuffixFormat.DecimalExponent;
return;
}
throw new ArgumentException("unable to parse quantity's suffix");
}
public SuffixFormat Format { get; }
public int Base { get; }
public int Exponent { get; }
public static string AppendMaxSuffix(Fraction value, SuffixFormat format)
{
if (value.IsZero)
{
return "0";
}
switch (format)
{
public static implicit operator decimal(ResourceQuantity v)
{
return v._unitlessValue.ToDecimal();
}
public static implicit operator ResourceQuantity(decimal v)
{
return new ResourceQuantity(v, 0, SuffixFormat.DecimalExponent);
}
#region suffixer
private class Suffixer
{
private static readonly IReadOnlyDictionary<string, (int, int)> BinSuffixes =
new Dictionary<string, (int, int)>
{
// Don't emit an error when trying to produce
// a suffix for 2^0.
{"", (2, 0)},
{"Ki", (2, 10)},
{"Mi", (2, 20)},
{"Gi", (2, 30)},
{"Ti", (2, 40)},
{"Pi", (2, 50)},
{"Ei", (2, 60)}
};
private static readonly IReadOnlyDictionary<string, (int, int)> DecSuffixes =
new Dictionary<string, (int, int)>
{
{"n", (10, -9)},
{"u", (10, -6)},
{"m", (10, -3)},
{"", (10, 0)},
{"k", (10, 3)},
{"M", (10, 6)},
{"G", (10, 9)},
{"T", (10, 12)},
{"P", (10, 15)},
{"E", (10, 18)}
};
public Suffixer(string suffix)
{
// looked up
{
if (DecSuffixes.TryGetValue(suffix, out var be))
{
(Base, Exponent) = be;
Format = SuffixFormat.DecimalSI;
return;
}
}
{
if (BinSuffixes.TryGetValue(suffix, out var be))
{
(Base, Exponent) = be;
Format = SuffixFormat.BinarySI;
return;
}
}
if (char.ToLower(suffix[0]) == 'e')
{
Base = 10;
Exponent = int.Parse(suffix.Substring(1));
Format = SuffixFormat.DecimalExponent;
return;
}
throw new ArgumentException("unable to parse quantity's suffix");
}
public SuffixFormat Format { get; }
public int Base { get; }
public int Exponent { get; }
public static string AppendMaxSuffix(Fraction value, SuffixFormat format)
{
if (value.IsZero)
{
return "0";
}
switch (format)
{
case SuffixFormat.DecimalExponent:
{
var minE = -9;
@@ -355,49 +355,49 @@ namespace k8s.Models
}
return $"{(decimal) lastv}e{minE}";
}
case SuffixFormat.BinarySI:
return AppendMaxSuffix(value, BinSuffixes);
case SuffixFormat.DecimalSI:
return AppendMaxSuffix(value, DecSuffixes);
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
private static string AppendMaxSuffix(Fraction value, IReadOnlyDictionary<string, (int, int)> suffixes)
{
var min = suffixes.First();
var suffix = min.Key;
var lastv = Roundup(value * Fraction.Pow(min.Value.Item1, -min.Value.Item2));
foreach (var kv in suffixes.Skip(1))
{
var v = value * Fraction.Pow(kv.Value.Item1, -kv.Value.Item2);
if (HasMantissa(v))
{
break;
}
suffix = kv.Key;
lastv = v;
}
return $"{(decimal) lastv}{suffix}";
}
private static Fraction Roundup(Fraction lastv)
{
var round = BigInteger.DivRem(lastv.Numerator, lastv.Denominator, out var remainder);
if (!remainder.IsZero)
{
lastv = round + 1;
}
return lastv;
}
}
#endregion
}
}
}
case SuffixFormat.BinarySI:
return AppendMaxSuffix(value, BinSuffixes);
case SuffixFormat.DecimalSI:
return AppendMaxSuffix(value, DecSuffixes);
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
private static string AppendMaxSuffix(Fraction value, IReadOnlyDictionary<string, (int, int)> suffixes)
{
var min = suffixes.First();
var suffix = min.Key;
var lastv = Roundup(value * Fraction.Pow(min.Value.Item1, -min.Value.Item2));
foreach (var kv in suffixes.Skip(1))
{
var v = value * Fraction.Pow(kv.Value.Item1, -kv.Value.Item2);
if (HasMantissa(v))
{
break;
}
suffix = kv.Key;
lastv = v;
}
return $"{(decimal) lastv}{suffix}";
}
private static Fraction Roundup(Fraction lastv)
{
var round = BigInteger.DivRem(lastv.Numerator, lastv.Denominator, out var remainder);
if (!remainder.IsZero)
{
lastv = round + 1;
}
return lastv;
}
}
#endregion
}
}

View File

@@ -1,52 +1,52 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace k8s.Models
{
public partial class V1Status
{
internal class V1StatusObjectViewConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var obj = JToken.Load(reader);
try
{
return obj.ToObject(objectType);
}
catch (JsonException)
{
// should be an object
}
return new V1Status
{
_original = obj,
HasObject = true
};
}
public override bool CanConvert(Type objectType)
{
return typeof(V1Status) == objectType;
}
}
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace k8s.Models
{
public partial class V1Status
{
internal class V1StatusObjectViewConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var obj = JToken.Load(reader);
try
{
return obj.ToObject(objectType);
}
catch (JsonException)
{
// should be an object
}
return new V1Status
{
_original = obj,
HasObject = true
};
}
public override bool CanConvert(Type objectType)
{
return typeof(V1Status) == objectType;
}
}
private JToken _original;
public bool HasObject { get; private set; }
public T ObjectView<T>()
{
return _original.ToObject<T>();
}
}
}
public bool HasObject { get; private set; }
public T ObjectView<T>()
{
return _original.ToObject<T>();
}
}
}

View File

@@ -1,90 +1,90 @@
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using k8s.Exceptions;
using k8s.Models;
using Microsoft.Rest;
using Microsoft.Rest.Serialization;
namespace k8s
{
public enum WatchEventType
{
[EnumMember(Value = "ADDED")] Added,
[EnumMember(Value = "MODIFIED")] Modified,
[EnumMember(Value = "DELETED")] Deleted,
[EnumMember(Value = "ERROR")] Error
}
public class Watcher<T> : IDisposable
{
/// <summary>
/// indicate if the watch object is alive
/// </summary>
public bool Watching { get; private set; }
private readonly CancellationTokenSource _cts;
private readonly StreamReader _streamReader;
private readonly Task _watcherLoop;
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using k8s.Exceptions;
using k8s.Models;
using Microsoft.Rest;
using Microsoft.Rest.Serialization;
/// <summary>
/// Initializes a new instance of the <see cref="Watcher{T}"/> class.
/// </summary>
namespace k8s
{
public enum WatchEventType
{
[EnumMember(Value = "ADDED")] Added,
[EnumMember(Value = "MODIFIED")] Modified,
[EnumMember(Value = "DELETED")] Deleted,
[EnumMember(Value = "ERROR")] Error
}
public class Watcher<T> : IDisposable
{
/// <summary>
/// indicate if the watch object is alive
/// </summary>
public bool Watching { get; private set; }
private readonly CancellationTokenSource _cts;
private readonly StreamReader _streamReader;
private readonly Task _watcherLoop;
/// <summary>
/// Initializes a new instance of the <see cref="Watcher{T}"/> class.
/// </summary>
/// <param name="streamReader">
/// A <see cref="StreamReader"/> from which to read the events.
/// </param>
/// <param name="onEvent">
/// The action to invoke when the server sends a new event.
/// </param>
/// <param name="onError">
/// The action to invoke when an error occurs.
/// </param>
/// <param name="onEvent">
/// The action to invoke when the server sends a new event.
/// </param>
/// <param name="onError">
/// The action to invoke when an error occurs.
/// </param>
/// <param name="onClosed">
/// The action to invoke when the server closes the connection.
/// </param>
public Watcher(StreamReader streamReader, Action<WatchEventType, T> onEvent, Action<Exception> onError, Action onClosed = null)
{
_streamReader = streamReader;
OnEvent += onEvent;
/// </param>
public Watcher(StreamReader streamReader, Action<WatchEventType, T> onEvent, Action<Exception> onError, Action onClosed = null)
{
_streamReader = streamReader;
OnEvent += onEvent;
OnError += onError;
OnClosed += onClosed;
_cts = new CancellationTokenSource();
_watcherLoop = this.WatcherLoop(_cts.Token);
}
OnClosed += onClosed;
/// <inheritdoc/>
public void Dispose()
{
_cts.Cancel();
_streamReader.Dispose();
}
/// <summary>
/// add/remove callbacks when any event raised from api server
/// </summary>
public event Action<WatchEventType, T> OnEvent;
/// <summary>
/// add/remove callbacks when any exception was caught during watching
/// </summary>
_cts = new CancellationTokenSource();
_watcherLoop = this.WatcherLoop(_cts.Token);
}
/// <inheritdoc/>
public void Dispose()
{
_cts.Cancel();
_streamReader.Dispose();
}
/// <summary>
/// add/remove callbacks when any event raised from api server
/// </summary>
public event Action<WatchEventType, T> OnEvent;
/// <summary>
/// add/remove callbacks when any exception was caught during watching
/// </summary>
public event Action<Exception> OnError;
/// <summary>
/// The event which is raised when the server closes th econnection.
/// <summary>
/// The event which is raised when the server closes th econnection.
/// </summary>
public event Action OnClosed;
public class WatchEvent
{
public WatchEventType Type { get; set; }
public T Object { get; set; }
public event Action OnClosed;
public class WatchEvent
{
public WatchEventType Type { get; set; }
public T Object { get; set; }
}
private async Task WatcherLoop(CancellationToken cancellationToken)
@@ -138,52 +138,52 @@ namespace k8s
Watching = false;
OnClosed?.Invoke();
}
}
}
public static class WatcherExt
{
/// <summary>
/// create a watch object from a call to api server with watch=true
/// </summary>
/// <typeparam name="T">type of the event object</typeparam>
/// <param name="response">the api response</param>
/// <param name="onEvent">a callback when any event raised from api server</param>
}
}
public static class WatcherExt
{
/// <summary>
/// create a watch object from a call to api server with watch=true
/// </summary>
/// <typeparam name="T">type of the event object</typeparam>
/// <param name="response">the api response</param>
/// <param name="onEvent">a callback when any event raised from api server</param>
/// <param name="onError">a callbak when any exception was caught during watching</param>
/// <param name="onClosed">
/// The action to invoke when the server closes the connection.
/// </param>
/// <returns>a watch object</returns>
public static Watcher<T> Watch<T>(this HttpOperationResponse response,
Action<WatchEventType, T> onEvent,
/// </param>
/// <returns>a watch object</returns>
public static Watcher<T> Watch<T>(this HttpOperationResponse response,
Action<WatchEventType, T> onEvent,
Action<Exception> onError = null,
Action onClosed = null)
{
if (!(response.Response.Content is WatcherDelegatingHandler.LineSeparatedHttpContent content))
{
throw new KubernetesClientException("not a watchable request or failed response");
}
return new Watcher<T>(content.StreamReader, onEvent, onError, onClosed);
}
/// <summary>
/// create a watch object from a call to api server with watch=true
/// </summary>
/// <typeparam name="T">type of the event object</typeparam>
/// <param name="response">the api response</param>
/// <param name="onEvent">a callback when any event raised from api server</param>
Action onClosed = null)
{
if (!(response.Response.Content is WatcherDelegatingHandler.LineSeparatedHttpContent content))
{
throw new KubernetesClientException("not a watchable request or failed response");
}
return new Watcher<T>(content.StreamReader, onEvent, onError, onClosed);
}
/// <summary>
/// create a watch object from a call to api server with watch=true
/// </summary>
/// <typeparam name="T">type of the event object</typeparam>
/// <param name="response">the api response</param>
/// <param name="onEvent">a callback when any event raised from api server</param>
/// <param name="onError">a callbak when any exception was caught during watching</param>
/// <param name="onClosed">
/// The action to invoke when the server closes the connection.
/// </param>
/// <returns>a watch object</returns>
public static Watcher<T> Watch<T>(this HttpOperationResponse<T> response,
Action<WatchEventType, T> onEvent,
/// </param>
/// <returns>a watch object</returns>
public static Watcher<T> Watch<T>(this HttpOperationResponse<T> response,
Action<WatchEventType, T> onEvent,
Action<Exception> onError = null,
Action onClosed = null)
{
return Watch((HttpOperationResponse)response, onEvent, onError, onClosed);
}
}
}
Action onClosed = null)
{
return Watch((HttpOperationResponse)response, onEvent, onError, onClosed);
}
}
}

View File

@@ -1,54 +1,54 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.WebUtilities;
namespace k8s
{
/// <summary>
/// This HttpDelegatingHandler is to rewrite the response and return first line to autorest client
/// then use WatchExt to create a watch object which interact with the replaced http response to get watch works.
/// </summary>
internal class WatcherDelegatingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var originResponse = await base.SendAsync(request, cancellationToken);
if (originResponse.IsSuccessStatusCode)
{
var query = QueryHelpers.ParseQuery(request.RequestUri.Query);
if (query.TryGetValue("watch", out var values) && values.Any(v => v == "true"))
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.WebUtilities;
namespace k8s
{
/// <summary>
/// This HttpDelegatingHandler is to rewrite the response and return first line to autorest client
/// then use WatchExt to create a watch object which interact with the replaced http response to get watch works.
/// </summary>
internal class WatcherDelegatingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var originResponse = await base.SendAsync(request, cancellationToken);
if (originResponse.IsSuccessStatusCode)
{
var query = QueryHelpers.ParseQuery(request.RequestUri.Query);
if (query.TryGetValue("watch", out var values) && values.Any(v => v == "true"))
{
originResponse.Content = new LineSeparatedHttpContent(originResponse.Content);
}
}
return originResponse;
}
internal class LineSeparatedHttpContent : HttpContent
{
private readonly HttpContent _originContent;
private Stream _originStream;
public LineSeparatedHttpContent(HttpContent originContent)
{
_originContent = originContent;
}
internal PeekableStreamReader StreamReader { get; private set; }
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
_originStream = await _originContent.ReadAsStreamAsync();
}
}
return originResponse;
}
internal class LineSeparatedHttpContent : HttpContent
{
private readonly HttpContent _originContent;
private Stream _originStream;
public LineSeparatedHttpContent(HttpContent originContent)
{
_originContent = originContent;
}
internal PeekableStreamReader StreamReader { get; private set; }
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
_originStream = await _originContent.ReadAsStreamAsync();
StreamReader = new PeekableStreamReader(_originStream);
var firstLine = await StreamReader.PeekLineAsync();
@@ -56,17 +56,17 @@ namespace k8s
var writer = new StreamWriter(stream);
// using (writer) // leave open
{
await writer.WriteAsync(firstLine);
await writer.FlushAsync();
}
}
protected override bool TryComputeLength(out long length)
{
length = 0;
return false;
}
{
await writer.WriteAsync(firstLine);
await writer.FlushAsync();
}
}
protected override bool TryComputeLength(out long length)
{
length = 0;
return false;
}
}
internal class PeekableStreamReader : StreamReader
{
@@ -128,6 +128,6 @@ namespace k8s
{
throw new NotImplementedException();
}
}
}
}
}

View File

@@ -1,4 +1,3 @@
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]

View File

@@ -1,369 +1,369 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using k8s.Models;
using k8s.Tests.Mock;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Rest;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Xunit;
using Xunit.Abstractions;
namespace k8s.Tests
{
public class AuthTests
{
private readonly ITestOutputHelper testOutput;
public AuthTests(ITestOutputHelper testOutput)
{
this.testOutput = testOutput;
}
private static HttpOperationResponse<V1PodList> ExecuteListPods(IKubernetes client)
{
return client.ListNamespacedPodWithHttpMessagesAsync("default").Result;
}
[Fact]
public void Anonymous()
{
using (var server = new MockKubeApiServer(testOutput))
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var listTask = ExecuteListPods(client);
Assert.True(listTask.Response.IsSuccessStatusCode);
Assert.Equal(1, listTask.Body.Items.Count);
}
using (var server = new MockKubeApiServer(testOutput, cxt =>
{
cxt.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.FromResult(false);
}))
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
}
[Fact]
public void BasicAuth()
{
const string testName = "test_name";
const string testPassword = "test_password";
using (var server = new MockKubeApiServer(testOutput, cxt =>
{
var header = cxt.Request.Headers["Authorization"].FirstOrDefault();
var expect = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{testName}:{testPassword}")))
.ToString();
if (header != expect)
{
cxt.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.FromResult(false);
}
return Task.FromResult(true);
}))
{
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = testName,
Password = testPassword
});
var listTask = ExecuteListPods(client);
Assert.True(listTask.Response.IsSuccessStatusCode);
Assert.Equal(1, listTask.Body.Items.Count);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = "wrong name",
Password = testPassword
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = testName,
Password = "wrong password"
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = "both wrong",
Password = "wrong password"
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = "xx"
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
}
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using k8s.Models;
using k8s.Tests.Mock;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Rest;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Xunit;
using Xunit.Abstractions;
namespace k8s.Tests
{
public class AuthTests
{
private readonly ITestOutputHelper testOutput;
public AuthTests(ITestOutputHelper testOutput)
{
this.testOutput = testOutput;
}
#if NETCOREAPP2_1 // The functionality under test, here, is dependent on managed HTTP / WebSocket functionality in .NET Core 2.1 or newer.
private static HttpOperationResponse<V1PodList> ExecuteListPods(IKubernetes client)
{
return client.ListNamespacedPodWithHttpMessagesAsync("default").Result;
}
[Fact]
public void Cert()
{
var serverCertificateData = File.ReadAllText("assets/apiserver-pfx-data.txt");
var clientCertificateKeyData = File.ReadAllText("assets/client-key-data.txt");
var clientCertificateData = File.ReadAllText("assets/client-certificate-data.txt");
X509Certificate2 serverCertificate = null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
using (MemoryStream serverCertificateStream = new MemoryStream(Convert.FromBase64String(serverCertificateData)))
{
serverCertificate = OpenCertificateStore(serverCertificateStream);
}
}
else
{
serverCertificate = new X509Certificate2(Convert.FromBase64String(serverCertificateData), "");
[Fact]
public void Anonymous()
{
using (var server = new MockKubeApiServer(testOutput))
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var listTask = ExecuteListPods(client);
Assert.True(listTask.Response.IsSuccessStatusCode);
Assert.Equal(1, listTask.Body.Items.Count);
}
using (var server = new MockKubeApiServer(testOutput, cxt =>
{
cxt.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.FromResult(false);
}))
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
var clientCertificate = new X509Certificate2(Convert.FromBase64String(clientCertificateData), "");
var clientCertificateValidationCalled = false;
using (var server = new MockKubeApiServer(testOutput, listenConfigure: options =>
{
options.UseHttps(new HttpsConnectionAdapterOptions
{
ServerCertificate = serverCertificate,
ClientCertificateMode = ClientCertificateMode.RequireCertificate,
ClientCertificateValidation = (certificate, chain, valid) =>
{
clientCertificateValidationCalled = true;
return clientCertificate.Equals(certificate);
}
});
}))
{
{
clientCertificateValidationCalled = false;
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
ClientCertificateData = clientCertificateData,
ClientCertificateKeyData = clientCertificateKeyData,
SslCaCert = serverCertificate,
SkipTlsVerify = false
});
var listTask = ExecuteListPods(client);
Assert.True(clientCertificateValidationCalled);
Assert.True(listTask.Response.IsSuccessStatusCode);
Assert.Equal(1, listTask.Body.Items.Count);
}
{
clientCertificateValidationCalled = false;
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
ClientCertificateData = clientCertificateData,
ClientCertificateKeyData = clientCertificateKeyData,
SkipTlsVerify = true
});
var listTask = ExecuteListPods(client);
Assert.True(clientCertificateValidationCalled);
Assert.True(listTask.Response.IsSuccessStatusCode);
Assert.Equal(1, listTask.Body.Items.Count);
}
{
clientCertificateValidationCalled = false;
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
ClientCertificateFilePath = "assets/client.crt", // TODO amazoning why client.crt != client-data.txt
ClientKeyFilePath = "assets/client.key",
SkipTlsVerify = true
});
Assert.ThrowsAny<Exception>(() => ExecuteListPods(client));
Assert.True(clientCertificateValidationCalled);
}
{
clientCertificateValidationCalled = false;
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
SkipTlsVerify = true
});
Assert.ThrowsAny<Exception>(() => ExecuteListPods(client));
Assert.False(clientCertificateValidationCalled);
}
}
}
#endif // NETCOREAPP2_1
[Fact]
public void Token()
{
const string token = "testingtoken";
using (var server = new MockKubeApiServer(testOutput, cxt =>
{
var header = cxt.Request.Headers["Authorization"].FirstOrDefault();
var expect = new AuthenticationHeaderValue("Bearer", token).ToString();
if (header != expect)
{
cxt.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.FromResult(false);
}
return Task.FromResult(true);
}))
{
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
AccessToken = token
});
var listTask = ExecuteListPods(client);
Assert.True(listTask.Response.IsSuccessStatusCode);
Assert.Equal(1, listTask.Body.Items.Count);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
AccessToken = "wrong token"
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = "wrong name",
Password = "same password"
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
}
}
private X509Certificate2 OpenCertificateStore(Stream stream)
{
Pkcs12Store store = new Pkcs12Store();
store.Load(stream, new char[] { });
var keyAlias = store.Aliases.Cast<string>().SingleOrDefault(a => store.IsKeyEntry(a));
var key = (RsaPrivateCrtKeyParameters)store.GetKey(keyAlias).Key;
var bouncyCertificate = store.GetCertificate(keyAlias).Certificate;
var certificate = new X509Certificate2(DotNetUtilities.ToX509Certificate(bouncyCertificate));
var parameters = DotNetUtilities.ToRSAParameters(key);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(parameters);
certificate = RSACertificateExtensions.CopyWithPrivateKey(certificate, rsa);
return certificate;
}
}
}
[Fact]
public void BasicAuth()
{
const string testName = "test_name";
const string testPassword = "test_password";
using (var server = new MockKubeApiServer(testOutput, cxt =>
{
var header = cxt.Request.Headers["Authorization"].FirstOrDefault();
var expect = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{testName}:{testPassword}")))
.ToString();
if (header != expect)
{
cxt.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.FromResult(false);
}
return Task.FromResult(true);
}))
{
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = testName,
Password = testPassword
});
var listTask = ExecuteListPods(client);
Assert.True(listTask.Response.IsSuccessStatusCode);
Assert.Equal(1, listTask.Body.Items.Count);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = "wrong name",
Password = testPassword
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = testName,
Password = "wrong password"
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = "both wrong",
Password = "wrong password"
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = "xx"
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
}
}
#if NETCOREAPP2_1 // The functionality under test, here, is dependent on managed HTTP / WebSocket functionality in .NET Core 2.1 or newer.
[Fact]
public void Cert()
{
var serverCertificateData = File.ReadAllText("assets/apiserver-pfx-data.txt");
var clientCertificateKeyData = File.ReadAllText("assets/client-key-data.txt");
var clientCertificateData = File.ReadAllText("assets/client-certificate-data.txt");
X509Certificate2 serverCertificate = null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
using (MemoryStream serverCertificateStream = new MemoryStream(Convert.FromBase64String(serverCertificateData)))
{
serverCertificate = OpenCertificateStore(serverCertificateStream);
}
}
else
{
serverCertificate = new X509Certificate2(Convert.FromBase64String(serverCertificateData), "");
}
var clientCertificate = new X509Certificate2(Convert.FromBase64String(clientCertificateData), "");
var clientCertificateValidationCalled = false;
using (var server = new MockKubeApiServer(testOutput, listenConfigure: options =>
{
options.UseHttps(new HttpsConnectionAdapterOptions
{
ServerCertificate = serverCertificate,
ClientCertificateMode = ClientCertificateMode.RequireCertificate,
ClientCertificateValidation = (certificate, chain, valid) =>
{
clientCertificateValidationCalled = true;
return clientCertificate.Equals(certificate);
}
});
}))
{
{
clientCertificateValidationCalled = false;
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
ClientCertificateData = clientCertificateData,
ClientCertificateKeyData = clientCertificateKeyData,
SslCaCert = serverCertificate,
SkipTlsVerify = false
});
var listTask = ExecuteListPods(client);
Assert.True(clientCertificateValidationCalled);
Assert.True(listTask.Response.IsSuccessStatusCode);
Assert.Equal(1, listTask.Body.Items.Count);
}
{
clientCertificateValidationCalled = false;
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
ClientCertificateData = clientCertificateData,
ClientCertificateKeyData = clientCertificateKeyData,
SkipTlsVerify = true
});
var listTask = ExecuteListPods(client);
Assert.True(clientCertificateValidationCalled);
Assert.True(listTask.Response.IsSuccessStatusCode);
Assert.Equal(1, listTask.Body.Items.Count);
}
{
clientCertificateValidationCalled = false;
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
ClientCertificateFilePath = "assets/client.crt", // TODO amazoning why client.crt != client-data.txt
ClientKeyFilePath = "assets/client.key",
SkipTlsVerify = true
});
Assert.ThrowsAny<Exception>(() => ExecuteListPods(client));
Assert.True(clientCertificateValidationCalled);
}
{
clientCertificateValidationCalled = false;
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
SkipTlsVerify = true
});
Assert.ThrowsAny<Exception>(() => ExecuteListPods(client));
Assert.False(clientCertificateValidationCalled);
}
}
}
#endif // NETCOREAPP2_1
[Fact]
public void Token()
{
const string token = "testingtoken";
using (var server = new MockKubeApiServer(testOutput, cxt =>
{
var header = cxt.Request.Headers["Authorization"].FirstOrDefault();
var expect = new AuthenticationHeaderValue("Bearer", token).ToString();
if (header != expect)
{
cxt.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.FromResult(false);
}
return Task.FromResult(true);
}))
{
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
AccessToken = token
});
var listTask = ExecuteListPods(client);
Assert.True(listTask.Response.IsSuccessStatusCode);
Assert.Equal(1, listTask.Body.Items.Count);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
AccessToken = "wrong token"
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString(),
Username = "wrong name",
Password = "same password"
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var listTask = ExecuteListPods(client);
Assert.Equal(HttpStatusCode.Unauthorized, listTask.Response.StatusCode);
}
}
}
private X509Certificate2 OpenCertificateStore(Stream stream)
{
Pkcs12Store store = new Pkcs12Store();
store.Load(stream, new char[] { });
var keyAlias = store.Aliases.Cast<string>().SingleOrDefault(a => store.IsKeyEntry(a));
var key = (RsaPrivateCrtKeyParameters)store.GetKey(keyAlias).Key;
var bouncyCertificate = store.GetCertificate(keyAlias).Certificate;
var certificate = new X509Certificate2(DotNetUtilities.ToX509Certificate(bouncyCertificate));
var parameters = DotNetUtilities.ToRSAParameters(key);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(parameters);
certificate = RSACertificateExtensions.CopyWithPrivateKey(certificate, rsa);
return certificate;
}
}
}

View File

@@ -1,74 +1,74 @@
using System;
using Xunit;
using k8s;
using System.IO;
namespace k8s.Tests
{
public class CertUtilsTests
{
/// <summary>
using System;
using Xunit;
using k8s;
using System.IO;
namespace k8s.Tests
{
public class CertUtilsTests
{
/// <summary>
/// This file contains a sample kubeconfig file. The paths to the certificate files are relative
/// to the current working directly.
/// </summary>
/// to the current working directly.
/// </summary>
private static readonly string kubeConfigFileName = "assets/kubeconfig.yml";
/// <summary>
/// <summary>
/// This file contains a sample kubeconfig file. The paths to the certificate files are relative
/// to the directory in which the kubeconfig file is located.
/// </summary>
private static readonly string kubeConfigWithRelativePathsFileName = "assets/kubeconfig.relative.yml";
/// <summary>
/// Checks that a certificate can be loaded from files.
/// </summary>
[Fact]
public void LoadFromFiles()
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigFileName, "federal-context", useRelativePaths: false);
// Just validate that this doesn't throw and private key is non-null
var cert = CertUtils.GeneratePfx(cfg);
Assert.NotNull(cert.PrivateKey);
/// to the directory in which the kubeconfig file is located.
/// </summary>
private static readonly string kubeConfigWithRelativePathsFileName = "assets/kubeconfig.relative.yml";
/// <summary>
/// Checks that a certificate can be loaded from files.
/// </summary>
[Fact]
public void LoadFromFiles()
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigFileName, "federal-context", useRelativePaths: false);
// Just validate that this doesn't throw and private key is non-null
var cert = CertUtils.GeneratePfx(cfg);
Assert.NotNull(cert.PrivateKey);
}
/// <summary>
/// Checks that a certificate can be loaded from files, in a scenario where the files are using relative paths.
/// </summary>
[Fact]
/// <summary>
/// Checks that a certificate can be loaded from files, in a scenario where the files are using relative paths.
/// </summary>
[Fact]
public void LoadFromFilesRelativePath()
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigWithRelativePathsFileName, "federal-context");
// Just validate that this doesn't throw and private key is non-null
var cert = CertUtils.GeneratePfx(cfg);
Assert.NotNull(cert.PrivateKey);
}
/// <summary>
/// Checks that a certificate can be loaded from inline.
/// </summary>
[Fact]
public void LoadFromInlineData()
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigFileName, "victorian-context", useRelativePaths: false);
// Just validate that this doesn't throw and private key is non-null
var cert = CertUtils.GeneratePfx(cfg);
Assert.NotNull(cert.PrivateKey);
}
/// <summary>
/// Checks that a certificate can be loaded from inline, in a scenario where the files are using relative paths..
/// </summary>
[Fact]
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigWithRelativePathsFileName, "federal-context");
// Just validate that this doesn't throw and private key is non-null
var cert = CertUtils.GeneratePfx(cfg);
Assert.NotNull(cert.PrivateKey);
}
/// <summary>
/// Checks that a certificate can be loaded from inline.
/// </summary>
[Fact]
public void LoadFromInlineData()
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigFileName, "victorian-context", useRelativePaths: false);
// Just validate that this doesn't throw and private key is non-null
var cert = CertUtils.GeneratePfx(cfg);
Assert.NotNull(cert.PrivateKey);
}
/// <summary>
/// Checks that a certificate can be loaded from inline, in a scenario where the files are using relative paths..
/// </summary>
[Fact]
public void LoadFromInlineDataRelativePath()
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigWithRelativePathsFileName, "victorian-context");
// Just validate that this doesn't throw and private key is non-null
var cert = CertUtils.GeneratePfx(cfg);
Assert.NotNull(cert.PrivateKey);
}
}
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigWithRelativePathsFileName, "victorian-context");
// Just validate that this doesn't throw and private key is non-null
var cert = CertUtils.GeneratePfx(cfg);
Assert.NotNull(cert.PrivateKey);
}
}
}

View File

@@ -1,39 +1,39 @@
using k8s.Models;
using Newtonsoft.Json;
using Xunit;
namespace k8s.Tests
{
public class IntOrStringTests
{
[Fact]
public void Serialize()
{
{
var v = 123;
IntstrIntOrString intorstr = v;
Assert.Equal("123", JsonConvert.SerializeObject(intorstr));
}
{
IntstrIntOrString intorstr = "12%";
Assert.Equal("\"12%\"", JsonConvert.SerializeObject(intorstr));
}
}
[Fact]
public void Deserialize()
{
{
var v = JsonConvert.DeserializeObject<IntstrIntOrString>("1234");
Assert.Equal("1234", v.Value);
}
{
var v = JsonConvert.DeserializeObject<IntstrIntOrString>("\"12%\"");
Assert.Equal("12%", v.Value);
}
using k8s.Models;
using Newtonsoft.Json;
using Xunit;
namespace k8s.Tests
{
public class IntOrStringTests
{
[Fact]
public void Serialize()
{
{
var v = 123;
IntstrIntOrString intorstr = v;
Assert.Equal("123", JsonConvert.SerializeObject(intorstr));
}
{
IntstrIntOrString intorstr = "12%";
Assert.Equal("\"12%\"", JsonConvert.SerializeObject(intorstr));
}
}
}
}
[Fact]
public void Deserialize()
{
{
var v = JsonConvert.DeserializeObject<IntstrIntOrString>("1234");
Assert.Equal("1234", v.Value);
}
{
var v = JsonConvert.DeserializeObject<IntstrIntOrString>("\"12%\"");
Assert.Equal("12%", v.Value);
}
}
}
}

View File

@@ -1,445 +1,445 @@
using System.IO;
using System.Linq;
using k8s.Exceptions;
using k8s.KubeConfigModels;
using Xunit;
namespace k8s.Tests
{
public class KubernetesClientConfigurationTests
{
/// <summary>
/// Check if host is properly loaded, per context
/// </summary>
[Theory]
[InlineData("federal-context", "https://horse.org:4443")]
[InlineData("queen-anne-context", "https://pig.org:443")]
public void ContextHost(string context, string host)
{
var fi = new FileInfo("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context, useRelativePaths: false);
Assert.Equal(host, cfg.Host);
}
/// <summary>
/// Checks if user-based token is loaded properly from the config file, per context
/// </summary>
/// <param name="context"></param>
/// <param name="token"></param>
[Theory]
[InlineData("queen-anne-context", "black-token")]
public void ContextUserToken(string context, string token)
{
var fi = new FileInfo("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context);
Assert.Equal(context, cfg.CurrentContext);
Assert.Null(cfg.Username);
Assert.Equal(token, cfg.AccessToken);
}
/// <summary>
/// Checks if certificate-based authentication is loaded properly from the config file, per context
/// </summary>
/// <param name="context">Context to retreive the configuration</param>
/// <param name="clientCert">'client-certificate-data' node content</param>
/// <param name="clientCertKey">'client-key-data' content</param>
[Theory]
[InlineData("federal-context", "assets/client.crt", "assets/client.key")]
public void ContextCertificate(string context, string clientCert, string clientCertKey)
{
var fi = new FileInfo("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context, useRelativePaths: false);
Assert.Equal(context, cfg.CurrentContext);
Assert.Equal(cfg.ClientCertificateFilePath, clientCert);
Assert.Equal(cfg.ClientKeyFilePath, clientCertKey);
}
/// <summary>
/// Checks if certificate-based authentication is loaded properly from the config file, per context
/// </summary>
/// <param name="context">Context to retreive the configuration</param>
[Theory]
[InlineData("victorian-context")]
public void ClientData(string context)
{
var fi = new FileInfo("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context);
Assert.Equal(context, cfg.CurrentContext);
Assert.NotNull(cfg.SslCaCert);
Assert.Equal(File.ReadAllText("assets/client-certificate-data.txt"), cfg.ClientCertificateData);
Assert.Equal(File.ReadAllText("assets/client-key-data.txt"), cfg.ClientCertificateKeyData);
}
/// <summary>
/// Checks that a KubeConfigException is thrown when no certificate-authority-data is set and user do not require tls
/// skip
/// </summary>
[Fact]
public void CheckClusterTlsCorrectness()
{
var fi = new FileInfo("assets/kubeconfig.tls-no-skip-error.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// Checks that a KubeConfigException is thrown when no certificate-authority-data is set and user do not require tls
/// skip
/// </summary>
[Fact]
public void CheckClusterTlsSkipCorrectness()
{
var fi = new FileInfo("assets/kubeconfig.tls-skip.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi);
Assert.NotNull(cfg.Host);
Assert.Null(cfg.SslCaCert);
Assert.True(cfg.SkipTlsVerify);
}
/// <summary>
/// Checks that a KubeConfigException is thrown when the cluster defined in clusters and contexts do not match
/// </summary>
[Fact]
public void ClusterNameMissmatch()
{
var fi = new FileInfo("assets/kubeconfig.cluster-missmatch.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// Checks that a KubeConfigException is thrown when the clusters section is missing
/// </summary>
[Fact]
public void ClusterNotFound()
{
var fi = new FileInfo("assets/kubeconfig.no-cluster.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// The configuration file is not present. An KubeConfigException should be thrown
/// </summary>
[Fact]
public void ConfigurationFileNotFound()
{
var fi = new FileInfo("/path/to/nowhere");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// Test that an Exception is thrown when initializating a KubernetClientConfiguration whose config file Context is not
/// present
/// </summary>
[Fact]
public void ContextNotFound()
{
var fi = new FileInfo("assets/kubeconfig.yml");
Assert.Throws<KubeConfigException>(() =>
KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context-not-found"));
}
/// <summary>
/// Checks Host is loaded from the default configuration file
/// </summary>
[Fact]
public void DefaultConfigurationLoaded()
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(new FileInfo("assets/kubeconfig.yml"), useRelativePaths: false);
Assert.NotNull(cfg.Host);
}
/// <summary>
/// Checks that a KubeConfigException is thrown when incomplete user credentials
/// </summary>
[Fact]
public void IncompleteUserCredentials()
{
var fi = new FileInfo("assets/kubeconfig.no-credentials.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false));
}
/// <summary>
/// Test if KubeConfigException is thrown when no Contexts and we use the default context name
/// </summary>
[Fact]
public void NoContexts()
{
var fi = new FileInfo("assets/kubeconfig.no-context.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// Test if KubeConfigException is thrown when no Contexts are set and we specify a concrete context name
/// </summary>
[Fact]
public void NoContextsExplicit()
{
var fi = new FileInfo("assets/kubeconfig-no-context.yml");
Assert.Throws<KubeConfigException>(() =>
KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context"));
}
/// <summary>
/// Checks that a KubeConfigException is thrown when the server property is not set in cluster
/// </summary>
[Fact]
public void ServerNotFound()
{
var fi = new FileInfo("assets/kubeconfig.no-server.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// Checks user/password authentication information is read properly
/// </summary>
[Fact]
public void UserPasswordAuthentication()
{
var fi = new FileInfo("assets/kubeconfig.user-pass.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false);
Assert.Equal("admin", cfg.Username);
Assert.Equal("secret", cfg.Password);
}
/// <summary>
/// Checks that a KubeConfigException is thrown when user cannot be found in users
/// </summary>
[Fact]
public void UserNotFound()
{
var fi = new FileInfo("assets/kubeconfig.user-not-found.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false));
}
/// <summary>
/// Make sure that user is not a necessary field. set #issue 24
/// </summary>
[Fact]
public void EmptyUserNotFound()
{
var fi = new FileInfo("assets/kubeconfig.no-user.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false);
Assert.NotEmpty(cfg.Host);
}
/// <summary>
/// Make sure Host is replaced by masterUrl
/// </summary>
[Fact]
public void OverrideByMasterUrl()
{
var fi = new FileInfo("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, masterUrl: "http://test.server", useRelativePaths: false);
Assert.Equal("http://test.server", cfg.Host);
}
/// <summary>
/// Make sure that http urls are loaded even if insecure-skip-tls-verify === true
/// </summary>
[Fact]
public void SmartSkipTlsVerify()
{
var fi = new FileInfo("assets/kubeconfig.tls-skip-http.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi);
Assert.False(cfg.SkipTlsVerify);
Assert.Equal("http://horse.org", cfg.Host);
}
/// <summary>
/// Checks config could work well when current-context is not set but masterUrl is set. #issue 24
/// </summary>
[Fact]
public void NoCurrentContext()
{
var fi = new FileInfo("assets/kubeconfig.no-current-context.yml");
// failed if cannot infer any server host
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
// survive when masterUrl is set
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, masterUrl: "http://test.server");
Assert.Equal("http://test.server", cfg.Host);
}
/// <summary>
/// Checks that loading a configuration from a file leaves no outstanding handles to the file.
/// </summary>
/// <remarks>
/// This test fails only on Windows.
/// </remarks>
[Fact]
public void DeletedConfigurationFile()
{
var assetFileInfo = new FileInfo("assets/kubeconfig.yml");
var tempFileInfo = new FileInfo(Path.GetTempFileName());
File.Copy(assetFileInfo.FullName, tempFileInfo.FullName, /* overwrite: */ true);
KubernetesClientConfiguration config;
try
{
config = KubernetesClientConfiguration.BuildConfigFromConfigFile(tempFileInfo, useRelativePaths: false);
}
finally
{
File.Delete(tempFileInfo.FullName);
}
}
/// <summary>
/// Checks Host is loaded from the default configuration file as string
/// </summary>
[Fact]
public void DefaultConfigurationAsStringLoaded()
{
var filePath = "assets/kubeconfig.yml";
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null, useRelativePaths: false);
Assert.NotNull(cfg.Host);
}
/// <summary>
/// Checks Host is loaded from the default configuration file as stream
/// </summary>
[Fact]
public void DefaultConfigurationAsStreamLoaded()
{
using (var stream = File.OpenRead("assets/kubeconfig.yml"))
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(stream);
Assert.NotNull(cfg.Host);
}
}
/// <summary>
/// Checks users.as-user-extra is loaded correctly from a configuration file.
/// </summary>
[Fact]
public void AsUserExtra()
{
var filePath = "assets/kubeconfig.as-user-extra.yml";
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null, useRelativePaths: false);
Assert.NotNull(cfg.Host);
}
/// <summary>
/// Ensures Kube config file is loaded from explicit file
/// </summary>
[Fact]
public void LoadKubeConfigExplicitFilePath()
{
var txt = File.ReadAllText("assets/kubeconfig.yml");
var expectedCfg = Yaml.LoadFromString<K8SConfiguration>(txt);
var cfg = KubernetesClientConfiguration.LoadKubeConfig("assets/kubeconfig.yml");
Assert.NotNull(cfg);
AssertConfigEqual(expectedCfg, cfg);
}
[Fact]
public void LoadKubeConfigFileInfo()
{
var filePath = "assets/kubeconfig.yml";
var txt = File.ReadAllText(filePath);
var expectedCfg = Yaml.LoadFromString<K8SConfiguration>(txt);
var fileInfo = new FileInfo(filePath);
var cfg = KubernetesClientConfiguration.LoadKubeConfig(fileInfo);
Assert.NotNull(cfg);
AssertConfigEqual(expectedCfg, cfg);
}
[Fact]
public void LoadKubeConfigStream()
{
var filePath = "assets/kubeconfig.yml";
var txt = File.ReadAllText(filePath);
var expectedCfg = Yaml.LoadFromString<K8SConfiguration>(txt);
var fileInfo = new FileInfo(filePath);
K8SConfiguration cfg;
using (var stream = fileInfo.OpenRead())
{
cfg = KubernetesClientConfiguration.LoadKubeConfig(stream);
}
Assert.NotNull(cfg);
AssertConfigEqual(expectedCfg, cfg);
}
private void AssertConfigEqual(K8SConfiguration expected, K8SConfiguration actual)
{
Assert.Equal(expected.ApiVersion, actual.ApiVersion);
Assert.Equal(expected.CurrentContext, actual.CurrentContext);
foreach (var expectedContext in expected.Contexts)
{
// Will throw exception if not found
var actualContext = actual.Contexts.First(c => c.Name.Equals(expectedContext.Name));
AssertContextEqual(expectedContext, actualContext);
}
foreach (var expectedCluster in expected.Clusters)
{
// Will throw exception if not found
var actualCluster = actual.Clusters.First(c => c.Name.Equals(expectedCluster.Name));
AssertClusterEqual(expectedCluster, actualCluster);
}
foreach (var expectedUser in expected.Users)
{
// Will throw exception if not found
var actualUser = actual.Users.First(u => u.Name.Equals(expectedUser.Name));
AssertUserEqual(expectedUser, actualUser);
}
}
private void AssertContextEqual(Context expected, Context actual)
{
Assert.Equal(expected.Name, actual.Name);
Assert.Equal(expected.Namespace, actual.Namespace);
Assert.Equal(expected.ContextDetails.Cluster, actual.ContextDetails.Cluster);
Assert.Equal(expected.ContextDetails.User, actual.ContextDetails.User);
Assert.Equal(expected.ContextDetails.Namespace, actual.ContextDetails.Namespace);
}
private void AssertClusterEqual(Cluster expected, Cluster actual)
{
Assert.Equal(expected.Name, actual.Name);
Assert.Equal(expected.ClusterEndpoint.CertificateAuthority, actual.ClusterEndpoint.CertificateAuthority);
Assert.Equal(expected.ClusterEndpoint.CertificateAuthorityData, actual.ClusterEndpoint.CertificateAuthorityData);
Assert.Equal(expected.ClusterEndpoint.Server, actual.ClusterEndpoint.Server);
Assert.Equal(expected.ClusterEndpoint.SkipTlsVerify, actual.ClusterEndpoint.SkipTlsVerify);
}
private void AssertUserEqual(User expected, User actual)
{
Assert.Equal(expected.Name, actual.Name);
var expectedCreds = expected.UserCredentials;
var actualCreds = actual.UserCredentials;
Assert.Equal(expectedCreds.ClientCertificateData, actualCreds.ClientCertificateData);
Assert.Equal(expectedCreds.ClientCertificate, actualCreds.ClientCertificate);
Assert.Equal(expectedCreds.ClientKeyData, actualCreds.ClientKeyData);
Assert.Equal(expectedCreds.ClientKey, actualCreds.ClientKey);
Assert.Equal(expectedCreds.Token, actualCreds.Token);
Assert.Equal(expectedCreds.Impersonate, actualCreds.Impersonate);
Assert.Equal(expectedCreds.UserName, actualCreds.UserName);
Assert.Equal(expectedCreds.Password, actualCreds.Password);
Assert.True(expectedCreds.ImpersonateGroups.All(x => actualCreds.ImpersonateGroups.Contains(x)));
Assert.True(expectedCreds.ImpersonateUserExtra.All(x => actualCreds.ImpersonateUserExtra.Contains(x)));
if (expectedCreds.AuthProvider != null)
{
Assert.True(expectedCreds.AuthProvider.Config.All(x => actualCreds.AuthProvider.Config.Contains(x)));
}
}
}
}
using System.IO;
using System.Linq;
using k8s.Exceptions;
using k8s.KubeConfigModels;
using Xunit;
namespace k8s.Tests
{
public class KubernetesClientConfigurationTests
{
/// <summary>
/// Check if host is properly loaded, per context
/// </summary>
[Theory]
[InlineData("federal-context", "https://horse.org:4443")]
[InlineData("queen-anne-context", "https://pig.org:443")]
public void ContextHost(string context, string host)
{
var fi = new FileInfo("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context, useRelativePaths: false);
Assert.Equal(host, cfg.Host);
}
/// <summary>
/// Checks if user-based token is loaded properly from the config file, per context
/// </summary>
/// <param name="context"></param>
/// <param name="token"></param>
[Theory]
[InlineData("queen-anne-context", "black-token")]
public void ContextUserToken(string context, string token)
{
var fi = new FileInfo("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context);
Assert.Equal(context, cfg.CurrentContext);
Assert.Null(cfg.Username);
Assert.Equal(token, cfg.AccessToken);
}
/// <summary>
/// Checks if certificate-based authentication is loaded properly from the config file, per context
/// </summary>
/// <param name="context">Context to retreive the configuration</param>
/// <param name="clientCert">'client-certificate-data' node content</param>
/// <param name="clientCertKey">'client-key-data' content</param>
[Theory]
[InlineData("federal-context", "assets/client.crt", "assets/client.key")]
public void ContextCertificate(string context, string clientCert, string clientCertKey)
{
var fi = new FileInfo("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context, useRelativePaths: false);
Assert.Equal(context, cfg.CurrentContext);
Assert.Equal(cfg.ClientCertificateFilePath, clientCert);
Assert.Equal(cfg.ClientKeyFilePath, clientCertKey);
}
/// <summary>
/// Checks if certificate-based authentication is loaded properly from the config file, per context
/// </summary>
/// <param name="context">Context to retreive the configuration</param>
[Theory]
[InlineData("victorian-context")]
public void ClientData(string context)
{
var fi = new FileInfo("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context);
Assert.Equal(context, cfg.CurrentContext);
Assert.NotNull(cfg.SslCaCert);
Assert.Equal(File.ReadAllText("assets/client-certificate-data.txt"), cfg.ClientCertificateData);
Assert.Equal(File.ReadAllText("assets/client-key-data.txt"), cfg.ClientCertificateKeyData);
}
/// <summary>
/// Checks that a KubeConfigException is thrown when no certificate-authority-data is set and user do not require tls
/// skip
/// </summary>
[Fact]
public void CheckClusterTlsCorrectness()
{
var fi = new FileInfo("assets/kubeconfig.tls-no-skip-error.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// Checks that a KubeConfigException is thrown when no certificate-authority-data is set and user do not require tls
/// skip
/// </summary>
[Fact]
public void CheckClusterTlsSkipCorrectness()
{
var fi = new FileInfo("assets/kubeconfig.tls-skip.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi);
Assert.NotNull(cfg.Host);
Assert.Null(cfg.SslCaCert);
Assert.True(cfg.SkipTlsVerify);
}
/// <summary>
/// Checks that a KubeConfigException is thrown when the cluster defined in clusters and contexts do not match
/// </summary>
[Fact]
public void ClusterNameMissmatch()
{
var fi = new FileInfo("assets/kubeconfig.cluster-missmatch.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// Checks that a KubeConfigException is thrown when the clusters section is missing
/// </summary>
[Fact]
public void ClusterNotFound()
{
var fi = new FileInfo("assets/kubeconfig.no-cluster.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// The configuration file is not present. An KubeConfigException should be thrown
/// </summary>
[Fact]
public void ConfigurationFileNotFound()
{
var fi = new FileInfo("/path/to/nowhere");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// Test that an Exception is thrown when initializating a KubernetClientConfiguration whose config file Context is not
/// present
/// </summary>
[Fact]
public void ContextNotFound()
{
var fi = new FileInfo("assets/kubeconfig.yml");
Assert.Throws<KubeConfigException>(() =>
KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context-not-found"));
}
/// <summary>
/// Checks Host is loaded from the default configuration file
/// </summary>
[Fact]
public void DefaultConfigurationLoaded()
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(new FileInfo("assets/kubeconfig.yml"), useRelativePaths: false);
Assert.NotNull(cfg.Host);
}
/// <summary>
/// Checks that a KubeConfigException is thrown when incomplete user credentials
/// </summary>
[Fact]
public void IncompleteUserCredentials()
{
var fi = new FileInfo("assets/kubeconfig.no-credentials.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false));
}
/// <summary>
/// Test if KubeConfigException is thrown when no Contexts and we use the default context name
/// </summary>
[Fact]
public void NoContexts()
{
var fi = new FileInfo("assets/kubeconfig.no-context.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// Test if KubeConfigException is thrown when no Contexts are set and we specify a concrete context name
/// </summary>
[Fact]
public void NoContextsExplicit()
{
var fi = new FileInfo("assets/kubeconfig-no-context.yml");
Assert.Throws<KubeConfigException>(() =>
KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context"));
}
/// <summary>
/// Checks that a KubeConfigException is thrown when the server property is not set in cluster
/// </summary>
[Fact]
public void ServerNotFound()
{
var fi = new FileInfo("assets/kubeconfig.no-server.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
}
/// <summary>
/// Checks user/password authentication information is read properly
/// </summary>
[Fact]
public void UserPasswordAuthentication()
{
var fi = new FileInfo("assets/kubeconfig.user-pass.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false);
Assert.Equal("admin", cfg.Username);
Assert.Equal("secret", cfg.Password);
}
/// <summary>
/// Checks that a KubeConfigException is thrown when user cannot be found in users
/// </summary>
[Fact]
public void UserNotFound()
{
var fi = new FileInfo("assets/kubeconfig.user-not-found.yml");
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false));
}
/// <summary>
/// Make sure that user is not a necessary field. set #issue 24
/// </summary>
[Fact]
public void EmptyUserNotFound()
{
var fi = new FileInfo("assets/kubeconfig.no-user.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false);
Assert.NotEmpty(cfg.Host);
}
/// <summary>
/// Make sure Host is replaced by masterUrl
/// </summary>
[Fact]
public void OverrideByMasterUrl()
{
var fi = new FileInfo("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, masterUrl: "http://test.server", useRelativePaths: false);
Assert.Equal("http://test.server", cfg.Host);
}
/// <summary>
/// Make sure that http urls are loaded even if insecure-skip-tls-verify === true
/// </summary>
[Fact]
public void SmartSkipTlsVerify()
{
var fi = new FileInfo("assets/kubeconfig.tls-skip-http.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi);
Assert.False(cfg.SkipTlsVerify);
Assert.Equal("http://horse.org", cfg.Host);
}
/// <summary>
/// Checks config could work well when current-context is not set but masterUrl is set. #issue 24
/// </summary>
[Fact]
public void NoCurrentContext()
{
var fi = new FileInfo("assets/kubeconfig.no-current-context.yml");
// failed if cannot infer any server host
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
// survive when masterUrl is set
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, masterUrl: "http://test.server");
Assert.Equal("http://test.server", cfg.Host);
}
/// <summary>
/// Checks that loading a configuration from a file leaves no outstanding handles to the file.
/// </summary>
/// <remarks>
/// This test fails only on Windows.
/// </remarks>
[Fact]
public void DeletedConfigurationFile()
{
var assetFileInfo = new FileInfo("assets/kubeconfig.yml");
var tempFileInfo = new FileInfo(Path.GetTempFileName());
File.Copy(assetFileInfo.FullName, tempFileInfo.FullName, /* overwrite: */ true);
KubernetesClientConfiguration config;
try
{
config = KubernetesClientConfiguration.BuildConfigFromConfigFile(tempFileInfo, useRelativePaths: false);
}
finally
{
File.Delete(tempFileInfo.FullName);
}
}
/// <summary>
/// Checks Host is loaded from the default configuration file as string
/// </summary>
[Fact]
public void DefaultConfigurationAsStringLoaded()
{
var filePath = "assets/kubeconfig.yml";
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null, useRelativePaths: false);
Assert.NotNull(cfg.Host);
}
/// <summary>
/// Checks Host is loaded from the default configuration file as stream
/// </summary>
[Fact]
public void DefaultConfigurationAsStreamLoaded()
{
using (var stream = File.OpenRead("assets/kubeconfig.yml"))
{
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(stream);
Assert.NotNull(cfg.Host);
}
}
/// <summary>
/// Checks users.as-user-extra is loaded correctly from a configuration file.
/// </summary>
[Fact]
public void AsUserExtra()
{
var filePath = "assets/kubeconfig.as-user-extra.yml";
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null, useRelativePaths: false);
Assert.NotNull(cfg.Host);
}
/// <summary>
/// Ensures Kube config file is loaded from explicit file
/// </summary>
[Fact]
public void LoadKubeConfigExplicitFilePath()
{
var txt = File.ReadAllText("assets/kubeconfig.yml");
var expectedCfg = Yaml.LoadFromString<K8SConfiguration>(txt);
var cfg = KubernetesClientConfiguration.LoadKubeConfig("assets/kubeconfig.yml");
Assert.NotNull(cfg);
AssertConfigEqual(expectedCfg, cfg);
}
[Fact]
public void LoadKubeConfigFileInfo()
{
var filePath = "assets/kubeconfig.yml";
var txt = File.ReadAllText(filePath);
var expectedCfg = Yaml.LoadFromString<K8SConfiguration>(txt);
var fileInfo = new FileInfo(filePath);
var cfg = KubernetesClientConfiguration.LoadKubeConfig(fileInfo);
Assert.NotNull(cfg);
AssertConfigEqual(expectedCfg, cfg);
}
[Fact]
public void LoadKubeConfigStream()
{
var filePath = "assets/kubeconfig.yml";
var txt = File.ReadAllText(filePath);
var expectedCfg = Yaml.LoadFromString<K8SConfiguration>(txt);
var fileInfo = new FileInfo(filePath);
K8SConfiguration cfg;
using (var stream = fileInfo.OpenRead())
{
cfg = KubernetesClientConfiguration.LoadKubeConfig(stream);
}
Assert.NotNull(cfg);
AssertConfigEqual(expectedCfg, cfg);
}
private void AssertConfigEqual(K8SConfiguration expected, K8SConfiguration actual)
{
Assert.Equal(expected.ApiVersion, actual.ApiVersion);
Assert.Equal(expected.CurrentContext, actual.CurrentContext);
foreach (var expectedContext in expected.Contexts)
{
// Will throw exception if not found
var actualContext = actual.Contexts.First(c => c.Name.Equals(expectedContext.Name));
AssertContextEqual(expectedContext, actualContext);
}
foreach (var expectedCluster in expected.Clusters)
{
// Will throw exception if not found
var actualCluster = actual.Clusters.First(c => c.Name.Equals(expectedCluster.Name));
AssertClusterEqual(expectedCluster, actualCluster);
}
foreach (var expectedUser in expected.Users)
{
// Will throw exception if not found
var actualUser = actual.Users.First(u => u.Name.Equals(expectedUser.Name));
AssertUserEqual(expectedUser, actualUser);
}
}
private void AssertContextEqual(Context expected, Context actual)
{
Assert.Equal(expected.Name, actual.Name);
Assert.Equal(expected.Namespace, actual.Namespace);
Assert.Equal(expected.ContextDetails.Cluster, actual.ContextDetails.Cluster);
Assert.Equal(expected.ContextDetails.User, actual.ContextDetails.User);
Assert.Equal(expected.ContextDetails.Namespace, actual.ContextDetails.Namespace);
}
private void AssertClusterEqual(Cluster expected, Cluster actual)
{
Assert.Equal(expected.Name, actual.Name);
Assert.Equal(expected.ClusterEndpoint.CertificateAuthority, actual.ClusterEndpoint.CertificateAuthority);
Assert.Equal(expected.ClusterEndpoint.CertificateAuthorityData, actual.ClusterEndpoint.CertificateAuthorityData);
Assert.Equal(expected.ClusterEndpoint.Server, actual.ClusterEndpoint.Server);
Assert.Equal(expected.ClusterEndpoint.SkipTlsVerify, actual.ClusterEndpoint.SkipTlsVerify);
}
private void AssertUserEqual(User expected, User actual)
{
Assert.Equal(expected.Name, actual.Name);
var expectedCreds = expected.UserCredentials;
var actualCreds = actual.UserCredentials;
Assert.Equal(expectedCreds.ClientCertificateData, actualCreds.ClientCertificateData);
Assert.Equal(expectedCreds.ClientCertificate, actualCreds.ClientCertificate);
Assert.Equal(expectedCreds.ClientKeyData, actualCreds.ClientKeyData);
Assert.Equal(expectedCreds.ClientKey, actualCreds.ClientKey);
Assert.Equal(expectedCreds.Token, actualCreds.Token);
Assert.Equal(expectedCreds.Impersonate, actualCreds.Impersonate);
Assert.Equal(expectedCreds.UserName, actualCreds.UserName);
Assert.Equal(expectedCreds.Password, actualCreds.Password);
Assert.True(expectedCreds.ImpersonateGroups.All(x => actualCreds.ImpersonateGroups.Contains(x)));
Assert.True(expectedCreds.ImpersonateUserExtra.All(x => actualCreds.ImpersonateUserExtra.Contains(x)));
if (expectedCreds.AuthProvider != null)
{
Assert.True(expectedCreds.AuthProvider.Config.All(x => actualCreds.AuthProvider.Config.Contains(x)));
}
}
}
}

View File

@@ -1,244 +1,244 @@
using System;
using k8s.Models;
using Newtonsoft.Json;
using Xunit;
using static k8s.Models.ResourceQuantity.SuffixFormat;
namespace k8s.Tests
{
public class QuantityValueTests
{
[Fact]
public void Deserialize()
{
{
var q = JsonConvert.DeserializeObject<ResourceQuantity>("\"12k\"");
Assert.Equal(new ResourceQuantity(12000, 0, DecimalSI), q);
}
}
[Fact]
public void Parse()
{
foreach (var (input, expect) in new[]
{
("0", new ResourceQuantity(0, 0, DecimalSI)),
("0n", new ResourceQuantity(0, 0, DecimalSI)),
("0u", new ResourceQuantity(0, 0, DecimalSI)),
("0m", new ResourceQuantity(0, 0, DecimalSI)),
("0Ki", new ResourceQuantity(0, 0, BinarySI)),
("0k", new ResourceQuantity(0, 0, DecimalSI)),
("0Mi", new ResourceQuantity(0, 0, BinarySI)),
("0M", new ResourceQuantity(0, 0, DecimalSI)),
("0Gi", new ResourceQuantity(0, 0, BinarySI)),
("0G", new ResourceQuantity(0, 0, DecimalSI)),
("0Ti", new ResourceQuantity(0, 0, BinarySI)),
("0T", new ResourceQuantity(0, 0, DecimalSI)),
// Quantity less numbers are allowed
("1", new ResourceQuantity(1, 0, DecimalSI)),
// Binary suffixes
("1Ki", new ResourceQuantity(1024, 0, BinarySI)),
("8Ki", new ResourceQuantity(8 * 1024, 0, BinarySI)),
("7Mi", new ResourceQuantity(7 * 1024 * 1024, 0, BinarySI)),
("6Gi", new ResourceQuantity(6L * 1024 * 1024 * 1024, 0, BinarySI)),
("5Ti", new ResourceQuantity(5L * 1024 * 1024 * 1024 * 1024, 0, BinarySI)),
("4Pi", new ResourceQuantity(4L * 1024 * 1024 * 1024 * 1024 * 1024, 0, BinarySI)),
("3Ei", new ResourceQuantity(3L * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, 0, BinarySI)),
("10Ti", new ResourceQuantity(10L * 1024 * 1024 * 1024 * 1024, 0, BinarySI)),
("100Ti", new ResourceQuantity(100L * 1024 * 1024 * 1024 * 1024, 0, BinarySI)),
// Decimal suffixes
("5n", new ResourceQuantity(5, -9, DecimalSI)),
("4u", new ResourceQuantity(4, -6, DecimalSI)),
("3m", new ResourceQuantity(3, -3, DecimalSI)),
("9", new ResourceQuantity(9, 0, DecimalSI)),
("8k", new ResourceQuantity(8, 3, DecimalSI)),
("50k", new ResourceQuantity(5, 4, DecimalSI)),
("7M", new ResourceQuantity(7, 6, DecimalSI)),
("6G", new ResourceQuantity(6, 9, DecimalSI)),
("5T", new ResourceQuantity(5, 12, DecimalSI)),
("40T", new ResourceQuantity(4, 13, DecimalSI)),
("300T", new ResourceQuantity(3, 14, DecimalSI)),
("2P", new ResourceQuantity(2, 15, DecimalSI)),
("1E", new ResourceQuantity(1, 18, DecimalSI)),
// Decimal exponents
("1E-3", new ResourceQuantity(1, -3, DecimalExponent)),
("1e3", new ResourceQuantity(1, 3, DecimalExponent)),
("1E6", new ResourceQuantity(1, 6, DecimalExponent)),
("1e9", new ResourceQuantity(1, 9, DecimalExponent)),
("1E12", new ResourceQuantity(1, 12, DecimalExponent)),
("1e15", new ResourceQuantity(1, 15, DecimalExponent)),
("1E18", new ResourceQuantity(1, 18, DecimalExponent)),
// Nonstandard but still parsable
("1e14", new ResourceQuantity(1, 14, DecimalExponent)),
("1e13", new ResourceQuantity(1, 13, DecimalExponent)),
("1e3", new ResourceQuantity(1, 3, DecimalExponent)),
("100.035k", new ResourceQuantity(100035, 0, DecimalSI)),
// Things that look like floating point
("0.001", new ResourceQuantity(1, -3, DecimalSI)),
("0.0005k", new ResourceQuantity(5, -1, DecimalSI)),
("0.005", new ResourceQuantity(5, -3, DecimalSI)),
("0.05", new ResourceQuantity(5, -2, DecimalSI)),
("0.5", new ResourceQuantity(5, -1, DecimalSI)),
("0.00050k", new ResourceQuantity(5, -1, DecimalSI)),
("0.00500", new ResourceQuantity(5, -3, DecimalSI)),
("0.05000", new ResourceQuantity(5, -2, DecimalSI)),
("0.50000", new ResourceQuantity(5, -1, DecimalSI)),
("0.5e0", new ResourceQuantity(5, -1, DecimalExponent)),
("0.5e-1", new ResourceQuantity(5, -2, DecimalExponent)),
("0.5e-2", new ResourceQuantity(5, -3, DecimalExponent)),
("0.5e0", new ResourceQuantity(5, -1, DecimalExponent)),
("10.035M", new ResourceQuantity(10035, 3, DecimalSI)),
("1.2e3", new ResourceQuantity(12, 2, DecimalExponent)),
("1.3E+6", new ResourceQuantity(13, 5, DecimalExponent)),
("1.40e9", new ResourceQuantity(14, 8, DecimalExponent)),
("1.53E12", new ResourceQuantity(153, 10, DecimalExponent)),
("1.6e15", new ResourceQuantity(16, 14, DecimalExponent)),
("1.7E18", new ResourceQuantity(17, 17, DecimalExponent)),
("9.01", new ResourceQuantity(901, -2, DecimalSI)),
("8.1k", new ResourceQuantity(81, 2, DecimalSI)),
("7.123456M", new ResourceQuantity(7123456, 0, DecimalSI)),
("6.987654321G", new ResourceQuantity(6987654321, 0, DecimalSI)),
("5.444T", new ResourceQuantity(5444, 9, DecimalSI)),
("40.1T", new ResourceQuantity(401, 11, DecimalSI)),
("300.2T", new ResourceQuantity(3002, 11, DecimalSI)),
("2.5P", new ResourceQuantity(25, 14, DecimalSI)),
("1.01E", new ResourceQuantity(101, 16, DecimalSI)),
// Things that saturate/round
("3.001n", new ResourceQuantity(4, -9, DecimalSI)),
("1.1E-9", new ResourceQuantity(2, -9, DecimalExponent)),
("0.0000000001", new ResourceQuantity(1, -9, DecimalSI)),
("0.0000000005", new ResourceQuantity(1, -9, DecimalSI)),
("0.00000000050", new ResourceQuantity(1, -9, DecimalSI)),
("0.5e-9", new ResourceQuantity(1, -9, DecimalExponent)),
("0.9n", new ResourceQuantity(1, -9, DecimalSI)),
("0.00000012345", new ResourceQuantity(124, -9, DecimalSI)),
("0.00000012354", new ResourceQuantity(124, -9, DecimalSI)),
("9Ei", new ResourceQuantity(ResourceQuantity.MaxAllowed, 0, BinarySI)),
("9223372036854775807Ki", new ResourceQuantity(ResourceQuantity.MaxAllowed, 0, BinarySI)),
("12E", new ResourceQuantity(12, 18, DecimalSI)),
// We'll accept fractional binary stuff, too.
("100.035Ki", new ResourceQuantity(10243584, -2, BinarySI)),
("0.5Mi", new ResourceQuantity(.5m * 1024 * 1024, 0, BinarySI)),
("0.05Gi", new ResourceQuantity(536870912, -1, BinarySI)),
("0.025Ti", new ResourceQuantity(274877906944, -1, BinarySI)),
// Things written by trolls
("0.000000000001Ki", new ResourceQuantity(2, -9, DecimalSI)), // rounds up, changes format
(".001", new ResourceQuantity(1, -3, DecimalSI)),
(".0001k", new ResourceQuantity(100, -3, DecimalSI)),
("1.", new ResourceQuantity(1, 0, DecimalSI)),
("1.G", new ResourceQuantity(1, 9, DecimalSI))
})
{
Assert.Equal(expect.ToString(), new ResourceQuantity(input).ToString());
}
foreach (var s in new[]
{
"1.1.M",
"1+1.0M",
"0.1mi",
"0.1am",
"aoeu",
".5i",
"1i",
"-3.01i",
"-3.01e-"
// TODO support trailing whitespace is forbidden
// " 1",
// "1 "
})
{
Assert.ThrowsAny<Exception>(() => { new ResourceQuantity(s); });
}
}
using System;
using k8s.Models;
using Newtonsoft.Json;
using Xunit;
using static k8s.Models.ResourceQuantity.SuffixFormat;
namespace k8s.Tests
{
public class QuantityValueTests
{
[Fact]
public void Deserialize()
{
{
var q = JsonConvert.DeserializeObject<ResourceQuantity>("\"12k\"");
Assert.Equal(new ResourceQuantity(12000, 0, DecimalSI), q);
}
}
[Fact]
public void Parse()
{
foreach (var (input, expect) in new[]
{
("0", new ResourceQuantity(0, 0, DecimalSI)),
("0n", new ResourceQuantity(0, 0, DecimalSI)),
("0u", new ResourceQuantity(0, 0, DecimalSI)),
("0m", new ResourceQuantity(0, 0, DecimalSI)),
("0Ki", new ResourceQuantity(0, 0, BinarySI)),
("0k", new ResourceQuantity(0, 0, DecimalSI)),
("0Mi", new ResourceQuantity(0, 0, BinarySI)),
("0M", new ResourceQuantity(0, 0, DecimalSI)),
("0Gi", new ResourceQuantity(0, 0, BinarySI)),
("0G", new ResourceQuantity(0, 0, DecimalSI)),
("0Ti", new ResourceQuantity(0, 0, BinarySI)),
("0T", new ResourceQuantity(0, 0, DecimalSI)),
// Quantity less numbers are allowed
("1", new ResourceQuantity(1, 0, DecimalSI)),
// Binary suffixes
("1Ki", new ResourceQuantity(1024, 0, BinarySI)),
("8Ki", new ResourceQuantity(8 * 1024, 0, BinarySI)),
("7Mi", new ResourceQuantity(7 * 1024 * 1024, 0, BinarySI)),
("6Gi", new ResourceQuantity(6L * 1024 * 1024 * 1024, 0, BinarySI)),
("5Ti", new ResourceQuantity(5L * 1024 * 1024 * 1024 * 1024, 0, BinarySI)),
("4Pi", new ResourceQuantity(4L * 1024 * 1024 * 1024 * 1024 * 1024, 0, BinarySI)),
("3Ei", new ResourceQuantity(3L * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, 0, BinarySI)),
("10Ti", new ResourceQuantity(10L * 1024 * 1024 * 1024 * 1024, 0, BinarySI)),
("100Ti", new ResourceQuantity(100L * 1024 * 1024 * 1024 * 1024, 0, BinarySI)),
// Decimal suffixes
("5n", new ResourceQuantity(5, -9, DecimalSI)),
("4u", new ResourceQuantity(4, -6, DecimalSI)),
("3m", new ResourceQuantity(3, -3, DecimalSI)),
("9", new ResourceQuantity(9, 0, DecimalSI)),
("8k", new ResourceQuantity(8, 3, DecimalSI)),
("50k", new ResourceQuantity(5, 4, DecimalSI)),
("7M", new ResourceQuantity(7, 6, DecimalSI)),
("6G", new ResourceQuantity(6, 9, DecimalSI)),
("5T", new ResourceQuantity(5, 12, DecimalSI)),
("40T", new ResourceQuantity(4, 13, DecimalSI)),
("300T", new ResourceQuantity(3, 14, DecimalSI)),
("2P", new ResourceQuantity(2, 15, DecimalSI)),
("1E", new ResourceQuantity(1, 18, DecimalSI)),
// Decimal exponents
("1E-3", new ResourceQuantity(1, -3, DecimalExponent)),
("1e3", new ResourceQuantity(1, 3, DecimalExponent)),
("1E6", new ResourceQuantity(1, 6, DecimalExponent)),
("1e9", new ResourceQuantity(1, 9, DecimalExponent)),
("1E12", new ResourceQuantity(1, 12, DecimalExponent)),
("1e15", new ResourceQuantity(1, 15, DecimalExponent)),
("1E18", new ResourceQuantity(1, 18, DecimalExponent)),
// Nonstandard but still parsable
("1e14", new ResourceQuantity(1, 14, DecimalExponent)),
("1e13", new ResourceQuantity(1, 13, DecimalExponent)),
("1e3", new ResourceQuantity(1, 3, DecimalExponent)),
("100.035k", new ResourceQuantity(100035, 0, DecimalSI)),
// Things that look like floating point
("0.001", new ResourceQuantity(1, -3, DecimalSI)),
("0.0005k", new ResourceQuantity(5, -1, DecimalSI)),
("0.005", new ResourceQuantity(5, -3, DecimalSI)),
("0.05", new ResourceQuantity(5, -2, DecimalSI)),
("0.5", new ResourceQuantity(5, -1, DecimalSI)),
("0.00050k", new ResourceQuantity(5, -1, DecimalSI)),
("0.00500", new ResourceQuantity(5, -3, DecimalSI)),
("0.05000", new ResourceQuantity(5, -2, DecimalSI)),
("0.50000", new ResourceQuantity(5, -1, DecimalSI)),
("0.5e0", new ResourceQuantity(5, -1, DecimalExponent)),
("0.5e-1", new ResourceQuantity(5, -2, DecimalExponent)),
("0.5e-2", new ResourceQuantity(5, -3, DecimalExponent)),
("0.5e0", new ResourceQuantity(5, -1, DecimalExponent)),
("10.035M", new ResourceQuantity(10035, 3, DecimalSI)),
("1.2e3", new ResourceQuantity(12, 2, DecimalExponent)),
("1.3E+6", new ResourceQuantity(13, 5, DecimalExponent)),
("1.40e9", new ResourceQuantity(14, 8, DecimalExponent)),
("1.53E12", new ResourceQuantity(153, 10, DecimalExponent)),
("1.6e15", new ResourceQuantity(16, 14, DecimalExponent)),
("1.7E18", new ResourceQuantity(17, 17, DecimalExponent)),
("9.01", new ResourceQuantity(901, -2, DecimalSI)),
("8.1k", new ResourceQuantity(81, 2, DecimalSI)),
("7.123456M", new ResourceQuantity(7123456, 0, DecimalSI)),
("6.987654321G", new ResourceQuantity(6987654321, 0, DecimalSI)),
("5.444T", new ResourceQuantity(5444, 9, DecimalSI)),
("40.1T", new ResourceQuantity(401, 11, DecimalSI)),
("300.2T", new ResourceQuantity(3002, 11, DecimalSI)),
("2.5P", new ResourceQuantity(25, 14, DecimalSI)),
("1.01E", new ResourceQuantity(101, 16, DecimalSI)),
// Things that saturate/round
("3.001n", new ResourceQuantity(4, -9, DecimalSI)),
("1.1E-9", new ResourceQuantity(2, -9, DecimalExponent)),
("0.0000000001", new ResourceQuantity(1, -9, DecimalSI)),
("0.0000000005", new ResourceQuantity(1, -9, DecimalSI)),
("0.00000000050", new ResourceQuantity(1, -9, DecimalSI)),
("0.5e-9", new ResourceQuantity(1, -9, DecimalExponent)),
("0.9n", new ResourceQuantity(1, -9, DecimalSI)),
("0.00000012345", new ResourceQuantity(124, -9, DecimalSI)),
("0.00000012354", new ResourceQuantity(124, -9, DecimalSI)),
("9Ei", new ResourceQuantity(ResourceQuantity.MaxAllowed, 0, BinarySI)),
("9223372036854775807Ki", new ResourceQuantity(ResourceQuantity.MaxAllowed, 0, BinarySI)),
("12E", new ResourceQuantity(12, 18, DecimalSI)),
// We'll accept fractional binary stuff, too.
("100.035Ki", new ResourceQuantity(10243584, -2, BinarySI)),
("0.5Mi", new ResourceQuantity(.5m * 1024 * 1024, 0, BinarySI)),
("0.05Gi", new ResourceQuantity(536870912, -1, BinarySI)),
("0.025Ti", new ResourceQuantity(274877906944, -1, BinarySI)),
// Things written by trolls
("0.000000000001Ki", new ResourceQuantity(2, -9, DecimalSI)), // rounds up, changes format
(".001", new ResourceQuantity(1, -3, DecimalSI)),
(".0001k", new ResourceQuantity(100, -3, DecimalSI)),
("1.", new ResourceQuantity(1, 0, DecimalSI)),
("1.G", new ResourceQuantity(1, 9, DecimalSI))
})
{
Assert.Equal(expect.ToString(), new ResourceQuantity(input).ToString());
}
foreach (var s in new[]
{
"1.1.M",
"1+1.0M",
"0.1mi",
"0.1am",
"aoeu",
".5i",
"1i",
"-3.01i",
"-3.01e-"
// TODO support trailing whitespace is forbidden
// " 1",
// "1 "
})
{
Assert.ThrowsAny<Exception>(() => { new ResourceQuantity(s); });
}
}
[Fact]
public void ConstructorTest()
{
Assert.Throws<FormatException>(() => new ResourceQuantity(string.Empty));
}
[Fact]
public void QuantityString()
{
foreach (var (input, expect, alternate) in new[]
{
(new ResourceQuantity(1024 * 1024 * 1024, 0, BinarySI), "1Gi", "1024Mi"),
(new ResourceQuantity(300 * 1024 * 1024, 0, BinarySI), "300Mi", "307200Ki"),
(new ResourceQuantity(6 * 1024, 0, BinarySI), "6Ki", ""),
(new ResourceQuantity(1001 * 1024 * 1024 * 1024L, 0, BinarySI), "1001Gi", "1025024Mi"),
(new ResourceQuantity(1024 * 1024 * 1024 * 1024L, 0, BinarySI), "1Ti", "1024Gi"),
(new ResourceQuantity(5, 0, BinarySI), "5", "5000m"),
(new ResourceQuantity(500, -3, BinarySI), "500m", "0.5"),
(new ResourceQuantity(1, 9, DecimalSI), "1G", "1000M"),
(new ResourceQuantity(1000, 6, DecimalSI), "1G", "0.001T"),
(new ResourceQuantity(1000000, 3, DecimalSI), "1G", ""),
(new ResourceQuantity(1000000000, 0, DecimalSI), "1G", ""),
(new ResourceQuantity(1, -3, DecimalSI), "1m", "1000u"),
(new ResourceQuantity(80, -3, DecimalSI), "80m", ""),
(new ResourceQuantity(1080, -3, DecimalSI), "1080m", "1.08"),
(new ResourceQuantity(108, -2, DecimalSI), "1080m", "1080000000n"),
(new ResourceQuantity(10800, -4, DecimalSI), "1080m", ""),
(new ResourceQuantity(300, 6, DecimalSI), "300M", ""),
(new ResourceQuantity(1, 12, DecimalSI), "1T", ""),
(new ResourceQuantity(1234567, 6, DecimalSI), "1234567M", ""),
(new ResourceQuantity(1234567, -3, BinarySI), "1234567m", ""),
(new ResourceQuantity(3, 3, DecimalSI), "3k", ""),
(new ResourceQuantity(1025, 0, BinarySI), "1025", ""),
(new ResourceQuantity(0, 0, DecimalSI), "0", ""),
(new ResourceQuantity(0, 0, BinarySI), "0", ""),
(new ResourceQuantity(1, 9, DecimalExponent), "1e9", ".001e12"),
(new ResourceQuantity(1, -3, DecimalExponent), "1e-3", "0.001e0"),
(new ResourceQuantity(1, -9, DecimalExponent), "1e-9", "1000e-12"),
(new ResourceQuantity(80, -3, DecimalExponent), "80e-3", ""),
(new ResourceQuantity(300, 6, DecimalExponent), "300e6", ""),
(new ResourceQuantity(1, 12, DecimalExponent), "1e12", ""),
(new ResourceQuantity(1, 3, DecimalExponent), "1e3", ""),
(new ResourceQuantity(3, 3, DecimalExponent), "3e3", ""),
(new ResourceQuantity(3, 3, DecimalSI), "3k", ""),
(new ResourceQuantity(0, 0, DecimalExponent), "0", "00"),
(new ResourceQuantity(1, -9, DecimalSI), "1n", ""),
(new ResourceQuantity(80, -9, DecimalSI), "80n", ""),
(new ResourceQuantity(1080, -9, DecimalSI), "1080n", ""),
(new ResourceQuantity(108, -8, DecimalSI), "1080n", ""),
(new ResourceQuantity(10800, -10, DecimalSI), "1080n", ""),
(new ResourceQuantity(1, -6, DecimalSI), "1u", ""),
(new ResourceQuantity(80, -6, DecimalSI), "80u", ""),
(new ResourceQuantity(1080, -6, DecimalSI), "1080u", "")
})
{
Assert.Equal(expect, input.ToString());
Assert.Equal(expect, new ResourceQuantity(expect).ToString());
if (string.IsNullOrEmpty(alternate))
{
continue;
}
Assert.Equal(expect, new ResourceQuantity(alternate).ToString());
}
}
[Fact]
public void Serialize()
{
{
ResourceQuantity quantity = 12000;
Assert.Equal("\"12e3\"", JsonConvert.SerializeObject(quantity));
}
[Fact]
public void QuantityString()
{
foreach (var (input, expect, alternate) in new[]
{
(new ResourceQuantity(1024 * 1024 * 1024, 0, BinarySI), "1Gi", "1024Mi"),
(new ResourceQuantity(300 * 1024 * 1024, 0, BinarySI), "300Mi", "307200Ki"),
(new ResourceQuantity(6 * 1024, 0, BinarySI), "6Ki", ""),
(new ResourceQuantity(1001 * 1024 * 1024 * 1024L, 0, BinarySI), "1001Gi", "1025024Mi"),
(new ResourceQuantity(1024 * 1024 * 1024 * 1024L, 0, BinarySI), "1Ti", "1024Gi"),
(new ResourceQuantity(5, 0, BinarySI), "5", "5000m"),
(new ResourceQuantity(500, -3, BinarySI), "500m", "0.5"),
(new ResourceQuantity(1, 9, DecimalSI), "1G", "1000M"),
(new ResourceQuantity(1000, 6, DecimalSI), "1G", "0.001T"),
(new ResourceQuantity(1000000, 3, DecimalSI), "1G", ""),
(new ResourceQuantity(1000000000, 0, DecimalSI), "1G", ""),
(new ResourceQuantity(1, -3, DecimalSI), "1m", "1000u"),
(new ResourceQuantity(80, -3, DecimalSI), "80m", ""),
(new ResourceQuantity(1080, -3, DecimalSI), "1080m", "1.08"),
(new ResourceQuantity(108, -2, DecimalSI), "1080m", "1080000000n"),
(new ResourceQuantity(10800, -4, DecimalSI), "1080m", ""),
(new ResourceQuantity(300, 6, DecimalSI), "300M", ""),
(new ResourceQuantity(1, 12, DecimalSI), "1T", ""),
(new ResourceQuantity(1234567, 6, DecimalSI), "1234567M", ""),
(new ResourceQuantity(1234567, -3, BinarySI), "1234567m", ""),
(new ResourceQuantity(3, 3, DecimalSI), "3k", ""),
(new ResourceQuantity(1025, 0, BinarySI), "1025", ""),
(new ResourceQuantity(0, 0, DecimalSI), "0", ""),
(new ResourceQuantity(0, 0, BinarySI), "0", ""),
(new ResourceQuantity(1, 9, DecimalExponent), "1e9", ".001e12"),
(new ResourceQuantity(1, -3, DecimalExponent), "1e-3", "0.001e0"),
(new ResourceQuantity(1, -9, DecimalExponent), "1e-9", "1000e-12"),
(new ResourceQuantity(80, -3, DecimalExponent), "80e-3", ""),
(new ResourceQuantity(300, 6, DecimalExponent), "300e6", ""),
(new ResourceQuantity(1, 12, DecimalExponent), "1e12", ""),
(new ResourceQuantity(1, 3, DecimalExponent), "1e3", ""),
(new ResourceQuantity(3, 3, DecimalExponent), "3e3", ""),
(new ResourceQuantity(3, 3, DecimalSI), "3k", ""),
(new ResourceQuantity(0, 0, DecimalExponent), "0", "00"),
(new ResourceQuantity(1, -9, DecimalSI), "1n", ""),
(new ResourceQuantity(80, -9, DecimalSI), "80n", ""),
(new ResourceQuantity(1080, -9, DecimalSI), "1080n", ""),
(new ResourceQuantity(108, -8, DecimalSI), "1080n", ""),
(new ResourceQuantity(10800, -10, DecimalSI), "1080n", ""),
(new ResourceQuantity(1, -6, DecimalSI), "1u", ""),
(new ResourceQuantity(80, -6, DecimalSI), "80u", ""),
(new ResourceQuantity(1080, -6, DecimalSI), "1080u", "")
})
{
Assert.Equal(expect, input.ToString());
Assert.Equal(expect, new ResourceQuantity(expect).ToString());
if (string.IsNullOrEmpty(alternate))
{
continue;
}
Assert.Equal(expect, new ResourceQuantity(alternate).ToString());
}
}
[Fact]
public void Serialize()
{
{
ResourceQuantity quantity = 12000;
Assert.Equal("\"12e3\"", JsonConvert.SerializeObject(quantity));
}
}
[Fact]
@@ -253,6 +253,6 @@ namespace k8s.Tests
{
var value = Yaml.SaveToString(new ResourceQuantity(1, -1, DecimalSI));
Assert.Equal("100m", value);
}
}
}
}
}
}

View File

@@ -1,12 +1,12 @@
using k8s.Models;
using k8s.Tests.Mock;
using Newtonsoft.Json;
using Xunit;
using k8s.Models;
using k8s.Tests.Mock;
using Newtonsoft.Json;
using Xunit;
using Xunit.Abstractions;
namespace k8s.Tests
{
public class V1StatusObjectViewTests
namespace k8s.Tests
{
public class V1StatusObjectViewTests
{
private readonly ITestOutputHelper testOutput;
@@ -14,63 +14,63 @@ namespace k8s.Tests
{
this.testOutput = testOutput;
}
[Fact]
public void ReturnStatus()
{
var v1Status = new V1Status
{
Message = "test message",
Status = "test status"
};
using (var server = new MockKubeApiServer(testOutput, resp: JsonConvert.SerializeObject(v1Status)))
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var status = client.DeleteNamespace(new V1DeleteOptions(), "test");
Assert.False(status.HasObject);
Assert.Equal(v1Status.Message, status.Message);
Assert.Equal(v1Status.Status, status.Status);
}
}
[Fact]
public void ReturnObject()
{
var corev1Namespace = new V1Namespace()
{
Metadata = new V1ObjectMeta()
{
Name = "test name"
},
Status = new V1NamespaceStatus()
{
Phase = "test termating"
}
};
using (var server = new MockKubeApiServer(testOutput, resp: JsonConvert.SerializeObject(corev1Namespace)))
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var status = client.DeleteNamespace(new V1DeleteOptions(), "test");
Assert.True(status.HasObject);
var obj = status.ObjectView<V1Namespace>();
Assert.Equal(obj.Metadata.Name, corev1Namespace.Metadata.Name);
Assert.Equal(obj.Status.Phase, corev1Namespace.Status.Phase);
}
}
}
}
[Fact]
public void ReturnStatus()
{
var v1Status = new V1Status
{
Message = "test message",
Status = "test status"
};
using (var server = new MockKubeApiServer(testOutput, resp: JsonConvert.SerializeObject(v1Status)))
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var status = client.DeleteNamespace(new V1DeleteOptions(), "test");
Assert.False(status.HasObject);
Assert.Equal(v1Status.Message, status.Message);
Assert.Equal(v1Status.Status, status.Status);
}
}
[Fact]
public void ReturnObject()
{
var corev1Namespace = new V1Namespace()
{
Metadata = new V1ObjectMeta()
{
Name = "test name"
},
Status = new V1NamespaceStatus()
{
Phase = "test termating"
}
};
using (var server = new MockKubeApiServer(testOutput, resp: JsonConvert.SerializeObject(corev1Namespace)))
{
var client = new Kubernetes(new KubernetesClientConfiguration
{
Host = server.Uri.ToString()
});
var status = client.DeleteNamespace(new V1DeleteOptions(), "test");
Assert.True(status.HasObject);
var obj = status.ObjectView<V1Namespace>();
Assert.Equal(obj.Metadata.Name, corev1Namespace.Metadata.Name);
Assert.Equal(obj.Status.Phase, corev1Namespace.Status.Phase);
}
}
}
}

View File

@@ -475,7 +475,7 @@ namespace k8s.Tests
await Task.WhenAny(eventsReceived.WaitAsync(), Task.Delay(TestTimeout));
Assert.True(
eventsReceived.CurrentCount == 0,
eventsReceived.CurrentCount == 0,
"Timed out waiting for all events / errors to be received."
);
@@ -584,21 +584,21 @@ namespace k8s.Tests
Spec = new V1PodSpec()
{
Containers = new List<V1Container>()
{
new V1Container()
{
Image = "ubuntu/xenial",
Name = "runner",
Command = new List<string>()
{
"/bin/bash",
"-c",
"--"
},
Args = new List<string>()
{
"trap : TERM INT; sleep infinity & wait"
}
{
new V1Container()
{
Image = "ubuntu/xenial",
Name = "runner",
Command = new List<string>()
{
"/bin/bash",
"-c",
"--"
},
Args = new List<string>()
{
"trap : TERM INT; sleep infinity & wait"
}
}
},
RestartPolicy = "Never"

View File

@@ -17,9 +17,9 @@ metadata:
name: foo
";
var obj = Yaml.LoadFromString<V1Pod>(content);
var obj = Yaml.LoadFromString<V1Pod>(content);
Assert.Equal("foo", obj.Metadata.Name);
Assert.Equal("foo", obj.Metadata.Name);
}
[Fact]
@@ -89,10 +89,10 @@ spec:
- name: cpu-demo-ctr
image: vish/stress
resources:
limits:
cpu: ""1""
requests:
cpu: ""0.5""
limits:
cpu: ""1""
requests:
cpu: ""0.5""
args:
- -cpus
- ""2""";