From 7193936e4078ae199e20771fee40297610e8c983 Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Tue, 7 Nov 2017 20:23:27 +0800 Subject: [PATCH 1/6] watcher handler will auto patch to all ctor path --- ...netes.Auth.cs => Kubernetes.ConfigInit.cs} | 99 +++++++++++-------- 1 file changed, 60 insertions(+), 39 deletions(-) rename src/{Kubernetes.Auth.cs => Kubernetes.ConfigInit.cs} (57%) diff --git a/src/Kubernetes.Auth.cs b/src/Kubernetes.ConfigInit.cs similarity index 57% rename from src/Kubernetes.Auth.cs rename to src/Kubernetes.ConfigInit.cs index 987efad90..cb010628b 100644 --- a/src/Kubernetes.Auth.cs +++ b/src/Kubernetes.ConfigInit.cs @@ -1,38 +1,36 @@ using k8s.Models; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using k8s.Exceptions; +using Microsoft.Rest; namespace k8s { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Net.Http; - using System.Net.Security; - using System.Security.Cryptography.X509Certificates; - using System.Threading.Tasks; - using k8s.Exceptions; - using Microsoft.Rest; - - public partial class Kubernetes : ServiceClient, IKubernetes + public partial class Kubernetes { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// Optional. The delegating handlers to add to the http client pipeline. + /// Optional. The delegating handlers to add to the http client pipeline. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. /// - public Kubernetes(KubernetesClientConfiguration config) + public Kubernetes(KubernetesClientConfiguration config, params DelegatingHandler[] handlers) : this(handlers) { - this.Initialize(); - - this.CaCert = config.SslCaCert; - this.BaseUri = new Uri(config.Host); - - var handler = new HttpClientHandler(); + CaCert = config.SslCaCert; + BaseUri = new Uri(config.Host); if (BaseUri.Scheme == "https") { if (config.SkipTlsVerify) { - handler.ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; + HttpClientHandler.ServerCertificateCustomValidationCallback = + (sender, certificate, chain, sslPolicyErrors) => true; } else { @@ -41,21 +39,47 @@ public Kubernetes(KubernetesClientConfiguration config) throw new KubeConfigException("a CA must be set when SkipTlsVerify === false"); } - handler.ServerCertificateCustomValidationCallback = CertificateValidationCallBack; + HttpClientHandler.ServerCertificateCustomValidationCallback = CertificateValidationCallBack; } } // set credentails for the kubernernet client - this.SetCredentials(config, handler); - this.InitializeHttpClient(handler, new DelegatingHandler[]{new WatcherDelegatingHandler()}); - - DeserializationSettings.Converters.Add(new V1Status.V1StatusObjectViewConverter()); + SetCredentials(config, HttpClientHandler); + } + + private X509Certificate2 CaCert { get; } + + partial void CustomInitialize() + { + AppendDelegatingHandler(); + DeserializationSettings.Converters.Add(new V1Status.V1StatusObjectViewConverter()); } - private X509Certificate2 CaCert { get; set; } + private void AppendDelegatingHandler() 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; + } + } /// - /// Set credentials for the Client + /// Set credentials for the Client /// /// k8s client configuration /// http client handler for the rest client @@ -88,7 +112,7 @@ private void SetCredentials(KubernetesClientConfiguration config, HttpClientHand } /// - /// SSl Cert Validation Callback + /// SSl Cert Validation Callback /// /// sender /// client certificate @@ -97,10 +121,10 @@ private void SetCredentials(KubernetesClientConfiguration config, HttpClientHand /// true if valid cert [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "Unused by design")] private bool CertificateValidationCallBack( - object sender, - X509Certificate certificate, - X509Chain chain, - SslPolicyErrors sslPolicyErrors) + object sender, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) { // If the certificate is a valid, signed certificate, return true. if (sslPolicyErrors == SslPolicyErrors.None) @@ -114,16 +138,13 @@ private bool CertificateValidationCallBack( chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; // add all your extra certificate chain - chain.ChainPolicy.ExtraStore.Add(this.CaCert); + chain.ChainPolicy.ExtraStore.Add(CaCert); chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; - var isValid = chain.Build((X509Certificate2)certificate); + var isValid = chain.Build((X509Certificate2) certificate); return isValid; } - else - { - // In all other cases, return false. - return false; - } + // In all other cases, return false. + return false; } } } From e06dcc57dcbb59f34e7b6a66e7e211dc1049f846 Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Tue, 7 Nov 2017 20:42:31 +0800 Subject: [PATCH 2/6] testcases: ensure handlers work with watcher --- tests/WatchTests.cs | 58 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/WatchTests.cs b/tests/WatchTests.cs index 45e05bc7b..976a2a4eb 100644 --- a/tests/WatchTests.cs +++ b/tests/WatchTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using k8s.Exceptions; @@ -270,5 +271,62 @@ public void WatchServerDisconnect() Assert.False(watcher.Watching); Assert.IsType(exceptionCatched); } + + private class DummyHandler : DelegatingHandler + { + internal bool Called { get; private set; } + + protected override Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + Called = true; + return base.SendAsync(request, cancellationToken); + } + } + + [Fact] + public void TestWatchWithHandlers() + { + using (var server = new MockKubeApiServer(async httpContext => + { + await WriteStreamLine(httpContext, MockKubeApiServer.MockPodResponse); + await Task.Delay(TimeSpan.FromMilliseconds(100)); + + await WriteStreamLine(httpContext, MockAddedEventStreamLine); + await Task.Delay(TimeSpan.FromMilliseconds(100)); + + // make server alive, cannot set to int.max as of it would block response + await Task.Delay(TimeSpan.FromDays(1)); + return false; + })) + { + var handler1 = new DummyHandler(); + var handler2 = new DummyHandler(); + + var client = new Kubernetes(new KubernetesClientConfiguration + { + Host = server.Uri.ToString() + }, handler1, handler2); + + Assert.False(handler1.Called); + Assert.False(handler2.Called); + + var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).Result; + + var events = new HashSet(); + + var watcher = listTask.Watch( + (type, item) => { events.Add(type); } + ); + + // wait server yields all events + Thread.Sleep(TimeSpan.FromMilliseconds(500)); + + Assert.Contains(WatchEventType.Added, events); + + Assert.True(handler1.Called); + Assert.True(handler2.Called); + } + } } } From 5cb18e93347d9b52b29d0bb7b458aeeef62d051b Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Tue, 7 Nov 2017 20:54:13 +0800 Subject: [PATCH 3/6] remove ctor api of KubernetesClientConfiguration, should use factory style --- ...ubernetesClientConfiguration.ConfigFile.cs | 15 +------ src/KubernetesClientConfiguration.cs | 39 +++++++------------ tests/CertUtilsTests.cs | 4 +- tests/KubernetesClientConfigurationTests.cs | 34 ++++++++-------- 4 files changed, 35 insertions(+), 57 deletions(-) diff --git a/src/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClientConfiguration.ConfigFile.cs index 1dc5b07de..802476494 100644 --- a/src/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClientConfiguration.ConfigFile.cs @@ -24,17 +24,6 @@ public partial class KubernetesClientConfiguration ? Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), @".kube\config") : Path.Combine(Environment.GetEnvironmentVariable("HOME"), ".kube/config"); - /// - /// Initializes a new instance of the class. - /// - /// kubeconfig file info - /// Context to use from kube config - public KubernetesClientConfiguration(FileInfo kubeconfig = null, string currentContext = null) - { - var k8SConfig = LoadKubeConfig(kubeconfig ?? new FileInfo(KubeConfigDefaultLocation)); - this.Initialize(k8SConfig, currentContext); - } - /// /// Initializes a new instance of the from config file /// @@ -60,7 +49,7 @@ public static KubernetesClientConfiguration BuildConfigFromConfigFile(FileInfo k var k8SConfig = LoadKubeConfig(kubeconfig); var k8SConfiguration = new KubernetesClientConfiguration(); - k8SConfiguration.Initialize(k8SConfig); + k8SConfiguration.Initialize(k8SConfig, currentContext); if (!string.IsNullOrWhiteSpace(masterUrl)) { @@ -200,7 +189,7 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext) /// /// Loads Kube Config /// - /// Kube config file contents + /// Kube config file contents /// Instance of the class private static K8SConfiguration LoadKubeConfig(FileInfo kubeconfig) { diff --git a/src/KubernetesClientConfiguration.cs b/src/KubernetesClientConfiguration.cs index de0aaa315..b053d51f8 100644 --- a/src/KubernetesClientConfiguration.cs +++ b/src/KubernetesClientConfiguration.cs @@ -1,78 +1,67 @@ +using System.Security.Cryptography.X509Certificates; + namespace k8s { - using System; - using System.IO; - using System.Linq; - using System.Security.Cryptography.X509Certificates; - using k8s.Exceptions; - using k8s.KubeConfigModels; - using YamlDotNet.Serialization; - using System.Runtime.InteropServices; - /// - /// Represents a set of kubernetes client configuration settings + /// Represents a set of kubernetes client configuration settings /// public partial class KubernetesClientConfiguration { - public KubernetesClientConfiguration() - { - } - /// - /// Gets Host + /// Gets Host /// public string Host { get; set; } /// - /// Gets SslCaCert + /// Gets SslCaCert /// public X509Certificate2 SslCaCert { get; set; } /// - /// Gets ClientCertificateData + /// Gets ClientCertificateData /// public string ClientCertificateData { get; set; } /// - /// Gets ClientCertificate Key + /// Gets ClientCertificate Key /// public string ClientCertificateKeyData { get; set; } /// - /// Gets ClientCertificate filename + /// Gets ClientCertificate filename /// public string ClientCertificateFilePath { get; set; } /// - /// Gets ClientCertificate Key filename + /// Gets ClientCertificate Key filename /// public string ClientKeyFilePath { get; set; } /// - /// Gets a value indicating whether to skip ssl server cert validation + /// Gets a value indicating whether to skip ssl server cert validation /// public bool SkipTlsVerify { get; set; } /// - /// Gets or sets the HTTP user agent. + /// Gets or sets the HTTP user agent. /// /// Http user agent. public string UserAgent { get; set; } /// - /// Gets or sets the username (HTTP basic authentication). + /// Gets or sets the username (HTTP basic authentication). /// /// The username. public string Username { get; set; } /// - /// Gets or sets the password (HTTP basic authentication). + /// Gets or sets the password (HTTP basic authentication). /// /// The password. public string Password { get; set; } /// - /// Gets or sets the access token for OAuth2 authentication. + /// Gets or sets the access token for OAuth2 authentication. /// /// The access token. public string AccessToken { get; set; } diff --git a/tests/CertUtilsTests.cs b/tests/CertUtilsTests.cs index 3b3b83e15..ce290a726 100644 --- a/tests/CertUtilsTests.cs +++ b/tests/CertUtilsTests.cs @@ -19,7 +19,7 @@ public class CertUtilsTests public void LoadFromFiles() { var fi = new FileInfo(kubeConfigFileName); - var cfg = new KubernetesClientConfiguration(fi, "federal-context"); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "federal-context"); // Just validate that this doesn't throw and private key is non-null var cert = CertUtils.GeneratePfx(cfg); @@ -33,7 +33,7 @@ public void LoadFromFiles() public void LoadFromInlineData() { var fi = new FileInfo(kubeConfigFileName); - var cfg = new KubernetesClientConfiguration(fi, "victorian-context"); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "victorian-context"); // Just validate that this doesn't throw and private key is non-null var cert = CertUtils.GeneratePfx(cfg); diff --git a/tests/KubernetesClientConfigurationTests.cs b/tests/KubernetesClientConfigurationTests.cs index d21b8476c..68b78322d 100755 --- a/tests/KubernetesClientConfigurationTests.cs +++ b/tests/KubernetesClientConfigurationTests.cs @@ -64,7 +64,7 @@ public static string readLine(string fileName) public void ConfigurationFileNotFound() { var fi = new FileInfo("/path/to/nowhere"); - Assert.Throws(() => new KubernetesClientConfiguration(fi)); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// @@ -73,7 +73,7 @@ public void ConfigurationFileNotFound() [Fact] public void DefaultConfigurationLoaded() { - var cfg = new KubernetesClientConfiguration(new FileInfo(kubeConfigFileName)); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(new FileInfo(kubeConfigFileName)); Assert.NotNull(cfg.Host); } @@ -86,7 +86,7 @@ public void DefaultConfigurationLoaded() public void ContextHost(string context, string host) { var fi = new FileInfo(kubeConfigFileName); - var cfg = new KubernetesClientConfiguration(fi, context); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context); Assert.Equal(host, cfg.Host); } @@ -100,7 +100,7 @@ public void ContextHost(string context, string host) public void ContextUserToken(string context, string token) { var fi = new FileInfo(kubeConfigFileName); - var cfg = new KubernetesClientConfiguration(fi, context); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context); Assert.Equal(context, cfg.CurrentContext); Assert.Null(cfg.Username); Assert.Equal(token, cfg.AccessToken); @@ -117,7 +117,7 @@ public void ContextUserToken(string context, string token) public void ContextCertificateTest(string context, string clientCert, string clientCertKey) { var fi = new FileInfo(kubeConfigFileName); - var cfg = new KubernetesClientConfiguration(fi, context); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context); Assert.Equal(context, cfg.CurrentContext); Assert.Equal(cfg.ClientCertificateFilePath, clientCert); Assert.Equal(cfg.ClientKeyFilePath, clientCertKey); @@ -132,7 +132,7 @@ public void ContextCertificateTest(string context, string clientCert, string cli public void ClientDataTest(string context) { var fi = new FileInfo(kubeConfigFileName); - var cfg = new KubernetesClientConfiguration(fi, context); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context); Assert.Equal(context, cfg.CurrentContext); Assert.NotNull(cfg.SslCaCert); Assert.Equal(readLine("assets/client-certificate-data.txt"), cfg.ClientCertificateData); @@ -147,7 +147,7 @@ public void ClientDataTest(string context) public void ContextNotFound() { var fi = new FileInfo(kubeConfigFileName); - Assert.Throws(() => new KubernetesClientConfiguration(fi, "context-not-found")); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context-not-found")); } /// @@ -157,7 +157,7 @@ public void ContextNotFound() public void NoContexts() { var fi = new FileInfo(kubeConfigNoContexts); - Assert.Throws(() => new KubernetesClientConfiguration(fi)); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// @@ -167,7 +167,7 @@ public void NoContexts() public void NoContextsExplicit() { var fi = new FileInfo(kubeConfigNoContexts); - Assert.Throws(() => new KubernetesClientConfiguration(fi, "context")); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context")); } /// @@ -177,7 +177,7 @@ public void NoContextsExplicit() public void UserPasswordAuthentication() { var fi = new FileInfo(kubeConfigUserPassword); - var cfg = new KubernetesClientConfiguration(fi); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi); Assert.Equal("admin", cfg.Username); Assert.Equal("secret", cfg.Password); } @@ -189,7 +189,7 @@ public void UserPasswordAuthentication() public void IncompleteUserCredentials() { var fi = new FileInfo(kubeConfigNoCredentials); - Assert.Throws(() => new KubernetesClientConfiguration(fi)); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// @@ -199,7 +199,7 @@ public void IncompleteUserCredentials() public void ServerNotFound() { var fi = new FileInfo(kubeConfigNoServer); - Assert.Throws(() => new KubernetesClientConfiguration(fi)); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// @@ -209,7 +209,7 @@ public void ServerNotFound() public void ClusterNotFound() { var fi = new FileInfo(kubeConfigNoCluster); - Assert.Throws(() => new KubernetesClientConfiguration(fi)); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// @@ -219,7 +219,7 @@ public void ClusterNotFound() public void ClusterNameMissmatch() { var fi = new FileInfo(kubeConfigClusterMissmatch); - Assert.Throws(() => new KubernetesClientConfiguration(fi)); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// @@ -229,7 +229,7 @@ public void ClusterNameMissmatch() public void CheckClusterTlsCorrectness() { var fi = new FileInfo(kubeConfigTlsNoSkipError); - Assert.Throws(() => new KubernetesClientConfiguration(fi)); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// @@ -239,7 +239,7 @@ public void CheckClusterTlsCorrectness() public void CheckClusterTlsSkipCorrectness() { var fi = new FileInfo(kubeConfigTlsSkip); - var cfg = new KubernetesClientConfiguration(fi); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi); Assert.NotNull(cfg.Host); Assert.Null(cfg.SslCaCert); Assert.True(cfg.SkipTlsVerify); @@ -251,7 +251,7 @@ public void CheckClusterTlsSkipCorrectness() // [Fact] // public void ListDefaultNamespacedPod() // { - // var k8sClientConfig = new KubernetesClientConfiguration(); + // var k8sClientConfig = KubernetesClientConfiguration.BuildConfigFromConfigFile(); // IKubernetes client = new Kubernetes(k8sClientConfig); // var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default").Result; // var list = listTask.Body; From 83559d58542bb74f5cd36ff3285a6ccbc7420d97 Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Sat, 11 Nov 2017 03:50:30 +0800 Subject: [PATCH 4/6] clean up test case naming --- tests/KubernetesClientConfigurationTests.cs | 2 +- tests/V1StatusObjectViewTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/KubernetesClientConfigurationTests.cs b/tests/KubernetesClientConfigurationTests.cs index 68b78322d..62d24e7a1 100755 --- a/tests/KubernetesClientConfigurationTests.cs +++ b/tests/KubernetesClientConfigurationTests.cs @@ -129,7 +129,7 @@ public void ContextCertificateTest(string context, string clientCert, string cli /// Context to retreive the configuration [Theory] [InlineData("victorian-context")] - public void ClientDataTest(string context) + public void ClientData(string context) { var fi = new FileInfo(kubeConfigFileName); var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context); diff --git a/tests/V1StatusObjectViewTests.cs b/tests/V1StatusObjectViewTests.cs index 9874e9509..4b115b309 100644 --- a/tests/V1StatusObjectViewTests.cs +++ b/tests/V1StatusObjectViewTests.cs @@ -8,7 +8,7 @@ namespace k8s.Tests public class V1StatusObjectViewTests { [Fact] - public void TestReturnStatus() + public void ReturnStatus() { var v1Status = new V1Status { @@ -32,7 +32,7 @@ public void TestReturnStatus() } [Fact] - public void TestReturnObject() + public void ReturnObject() { var corev1Namespace = new Corev1Namespace() { From 4571723eefcd44fce71e10a9e7222d24daaf5b39 Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Sun, 12 Nov 2017 03:56:56 +0800 Subject: [PATCH 5/6] make load form yaml config file less sensitive --- src/KubeConfigModels/K8SConfiguration.cs | 10 +- src/Kubernetes.ConfigInit.cs | 17 ++- ...ubernetesClientConfiguration.ConfigFile.cs | 127 ++++++++++-------- 3 files changed, 89 insertions(+), 65 deletions(-) diff --git a/src/KubeConfigModels/K8SConfiguration.cs b/src/KubeConfigModels/K8SConfiguration.cs index 723b360d1..7d6184f96 100644 --- a/src/KubeConfigModels/K8SConfiguration.cs +++ b/src/KubeConfigModels/K8SConfiguration.cs @@ -1,4 +1,4 @@ -namespace k8s.KubeConfigModels +namespace k8s.KubeConfigModels { using System.Collections.Generic; using YamlDotNet.Serialization; @@ -9,7 +9,7 @@ public class K8SConfiguration { [YamlMember(Alias = "preferences")] - public IDictionary preferences{ get; set; } + public IDictionary Preferences{ get; set; } [YamlMember(Alias = "apiVersion")] public string ApiVersion { get; set; } @@ -21,12 +21,12 @@ public class K8SConfiguration public string CurrentContext { get; set; } [YamlMember(Alias = "contexts")] - public IEnumerable Contexts { get; set; } + public IEnumerable Contexts { get; set; } = new Context[0]; [YamlMember(Alias = "clusters")] - public IEnumerable Clusters { get; set; } + public IEnumerable Clusters { get; set; } = new Cluster[0]; [YamlMember(Alias = "users")] - public IEnumerable Users { get; set; } + public IEnumerable Users { get; set; } = new User[0]; } } diff --git a/src/Kubernetes.ConfigInit.cs b/src/Kubernetes.ConfigInit.cs index cb010628b..a95a6e3e0 100644 --- a/src/Kubernetes.ConfigInit.cs +++ b/src/Kubernetes.ConfigInit.cs @@ -1,10 +1,10 @@ -using k8s.Models; using System; using System.Diagnostics.CodeAnalysis; using System.Net.Http; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using k8s.Exceptions; +using k8s.Models; using Microsoft.Rest; namespace k8s @@ -22,8 +22,21 @@ public partial class Kubernetes /// 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; - BaseUri = new Uri(config.Host); if (BaseUri.Scheme == "https") { diff --git a/src/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClientConfiguration.ConfigFile.cs index 802476494..6ec531fb1 100644 --- a/src/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClientConfiguration.ConfigFile.cs @@ -12,12 +12,7 @@ namespace k8s public partial class KubernetesClientConfiguration { /// - /// Gets CurrentContext - /// - public string CurrentContext { get; private set; } - - /// - /// kubeconfig Default Location + /// kubeconfig Default Location /// private static readonly string KubeConfigDefaultLocation = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) @@ -25,22 +20,29 @@ public partial class KubernetesClientConfiguration : Path.Combine(Environment.GetEnvironmentVariable("HOME"), ".kube/config"); /// - /// Initializes a new instance of the from config file + /// Gets CurrentContext + /// + public string CurrentContext { get; private set; } + + /// + /// Initializes a new instance of the from config file /// /// kube api server endpoint /// kubeconfig filepath - public static KubernetesClientConfiguration BuildConfigFromConfigFile(string masterUrl = null, string kubeconfigPath = null) + public static KubernetesClientConfiguration BuildConfigFromConfigFile(string masterUrl = null, + string kubeconfigPath = null) { - return BuildConfigFromConfigFile(new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation), null, masterUrl); + return BuildConfigFromConfigFile(new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation), null, + masterUrl); } /// - /// /// /// Fileinfo of the kubeconfig, cannot be null /// override the context in config file, set null if do not want to override /// overrider kube api server endpoint, set null if do not want to override - public static KubernetesClientConfiguration BuildConfigFromConfigFile(FileInfo kubeconfig, string currentContext = null, string masterUrl = null) + public static KubernetesClientConfiguration BuildConfigFromConfigFile(FileInfo kubeconfig, + string currentContext = null, string masterUrl = null) { if (kubeconfig == null) { @@ -57,33 +59,18 @@ public static KubernetesClientConfiguration BuildConfigFromConfigFile(FileInfo k } return k8SConfiguration; } - /// - /// Validates and Intializes Client Configuration + /// Validates and Intializes Client Configuration /// /// Kubernetes Configuration /// Current Context private void Initialize(K8SConfiguration k8SConfig, string currentContext = null) { - if (k8SConfig.Contexts == null) - { - throw new KubeConfigException("No contexts found in kubeconfig"); - } - - if (k8SConfig.Clusters == null) - { - throw new KubeConfigException($"No clusters found in kubeconfig"); - } - - if (k8SConfig.Users == null) - { - throw new KubeConfigException($"No users found in kubeconfig"); - } - // current context currentContext = currentContext ?? k8SConfig.CurrentContext; - Context activeContext = + + var activeContext = k8SConfig.Contexts.FirstOrDefault( c => c.Name.Equals(currentContext, StringComparison.OrdinalIgnoreCase)); if (activeContext == null) @@ -91,12 +78,21 @@ private void Initialize(K8SConfiguration k8SConfig, string currentContext = null throw new KubeConfigException($"CurrentContext: {currentContext} not found in contexts in kubeconfig"); } - this.CurrentContext = activeContext.Name; + CurrentContext = activeContext.Name; // cluster + SetClusterDetails(k8SConfig, activeContext); + + // user + SetUserDetails(k8SConfig, activeContext); + } + + 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"); @@ -106,33 +102,49 @@ private void Initialize(K8SConfiguration k8SConfig, string currentContext = null { throw new KubeConfigException($"Server not found for current-context {activeContext} in kubeconfig"); } + Host = clusterDetails.ClusterEndpoint.Server; - 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"); - } + SkipTlsVerify = clusterDetails.ClusterEndpoint.SkipTlsVerify; - this.Host = clusterDetails.ClusterEndpoint.Server; - if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthorityData)) + try { - string data = clusterDetails.ClusterEndpoint.CertificateAuthorityData; - this.SslCaCert = new X509Certificate2(Convert.FromBase64String(data)); + 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(clusterDetails.ClusterEndpoint.CertificateAuthority); + } + } } - else if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthority)) + catch (UriFormatException e) { - this.SslCaCert = new X509Certificate2(clusterDetails.ClusterEndpoint.CertificateAuthority); + throw new KubeConfigException("Bad Server host url", e); } - this.SkipTlsVerify = clusterDetails.ClusterEndpoint.SkipTlsVerify; - - // user - this.SetUserDetails(k8SConfig, activeContext); } 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)); @@ -151,14 +163,14 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext) // Basic and bearer tokens are mutually exclusive if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.Token)) { - this.AccessToken = userDetails.UserCredentials.Token; + AccessToken = userDetails.UserCredentials.Token; userCredentialsFound = true; } else if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.UserName) && !string.IsNullOrWhiteSpace(userDetails.UserCredentials.Password)) { - this.Username = userDetails.UserCredentials.UserName; - this.Password = userDetails.UserCredentials.Password; + Username = userDetails.UserCredentials.UserName; + Password = userDetails.UserCredentials.Password; userCredentialsFound = true; } @@ -166,16 +178,16 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext) if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientCertificateData) && !string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientKeyData)) { - this.ClientCertificateData = userDetails.UserCredentials.ClientCertificateData; - this.ClientCertificateKeyData = userDetails.UserCredentials.ClientKeyData; + ClientCertificateData = userDetails.UserCredentials.ClientCertificateData; + ClientCertificateKeyData = userDetails.UserCredentials.ClientKeyData; userCredentialsFound = true; } if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientCertificate) && !string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientKey)) { - this.ClientCertificateFilePath = userDetails.UserCredentials.ClientCertificate; - this.ClientKeyFilePath = userDetails.UserCredentials.ClientKey; + ClientCertificateFilePath = userDetails.UserCredentials.ClientCertificate; + ClientKeyFilePath = userDetails.UserCredentials.ClientKey; userCredentialsFound = true; } @@ -187,7 +199,7 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext) } /// - /// Loads Kube Config + /// Loads Kube Config /// /// Kube config file contents /// Instance of the class @@ -197,11 +209,10 @@ private static K8SConfiguration LoadKubeConfig(FileInfo kubeconfig) { throw new KubeConfigException($"kubeconfig file not found at {kubeconfig.FullName}"); } - var kubeconfigContent = File.ReadAllText(kubeconfig.FullName); var deserializeBuilder = new DeserializerBuilder(); var deserializer = deserializeBuilder.Build(); - return deserializer.Deserialize(kubeconfigContent); + return deserializer.Deserialize(kubeconfig.OpenText()); } } -} +} From d692f2e7db4471097f130701abfc9397bd86cf6e Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Sun, 12 Nov 2017 04:42:37 +0800 Subject: [PATCH 6/6] clean up and add testcases for less sensitive config loader --- ...ubernetesClientConfiguration.ConfigFile.cs | 19 +- tests/KubernetesClientConfigurationTests.cs | 280 +++++++++--------- ...-context.yml => kubeconfig.no-context.yml} | 0 .../assets/kubeconfig.no-current-context.yml | 19 ++ tests/assets/kubeconfig.no-user.yml | 14 + tests/assets/kubeconfig.tls-skip-http.yml | 19 ++ tests/assets/kubeconfig.user-not-found.yml | 15 + 7 files changed, 223 insertions(+), 143 deletions(-) rename tests/assets/{kubeconfig-no-context.yml => kubeconfig.no-context.yml} (100%) create mode 100644 tests/assets/kubeconfig.no-current-context.yml create mode 100644 tests/assets/kubeconfig.no-user.yml create mode 100644 tests/assets/kubeconfig.tls-skip-http.yml create mode 100644 tests/assets/kubeconfig.user-not-found.yml diff --git a/src/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClientConfiguration.ConfigFile.cs index 6ec531fb1..559d07afd 100644 --- a/src/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClientConfiguration.ConfigFile.cs @@ -51,12 +51,25 @@ public static KubernetesClientConfiguration BuildConfigFromConfigFile(FileInfo k var k8SConfig = LoadKubeConfig(kubeconfig); var k8SConfiguration = new KubernetesClientConfiguration(); - k8SConfiguration.Initialize(k8SConfig, currentContext); + + 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; } @@ -65,11 +78,9 @@ public static KubernetesClientConfiguration BuildConfigFromConfigFile(FileInfo k /// /// Kubernetes Configuration /// Current Context - private void Initialize(K8SConfiguration k8SConfig, string currentContext = null) + private void InitializeContext(K8SConfiguration k8SConfig, string currentContext) { // current context - currentContext = currentContext ?? k8SConfig.CurrentContext; - var activeContext = k8SConfig.Contexts.FirstOrDefault( c => c.Name.Equals(currentContext, StringComparison.OrdinalIgnoreCase)); diff --git a/tests/KubernetesClientConfigurationTests.cs b/tests/KubernetesClientConfigurationTests.cs index 62d24e7a1..e996c05c3 100755 --- a/tests/KubernetesClientConfigurationTests.cs +++ b/tests/KubernetesClientConfigurationTests.cs @@ -1,97 +1,26 @@ -using Xunit; using System.IO; +using k8s.Exceptions; +using Xunit; namespace k8s.Tests { public class KubernetesClientConfigurationTests { - - public static string readLine(string fileName) - { - StreamReader reader = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read)); - return reader.ReadLine(); - } - - /// - /// This file contains a sample kubeconfig file - /// - private static readonly string kubeConfigFileName = "assets/kubeconfig.yml"; - - /// - /// Invalid test file with no context on purpose - /// - private static readonly string kubeConfigNoContexts = "assets/kubeconfig-no-context.yml"; - - /// - /// Sample configuration file with user/password authentication - /// - private static readonly string kubeConfigUserPassword = "assets/kubeconfig.user-pass.yml"; - - /// - /// Sample configuration file with incorrect user credentials structures on purpose - /// - private static readonly string kubeConfigNoCredentials = "assets/kubeconfig.no-credentials.yml"; - - /// - /// Sample configuration file with incorrect cluster/server structure on purpose - /// - private static readonly string kubeConfigNoServer = "assets/kubeconfig.no-server.yml"; - - /// - /// Sample configuration file with incorrect cluster/server structure on purpose - /// - private static readonly string kubeConfigNoCluster = "assets/kubeconfig.no-cluster.yml"; - - /// - /// Sample configuration file with incorrect match in cluster name - /// - private static readonly string kubeConfigClusterMissmatch = "assets/kubeconfig.cluster-missmatch.yml"; - - /// - /// Sample configuration file with incorrect TLS configuration in cluster section - /// - private static readonly string kubeConfigTlsNoSkipError = "assets/kubeconfig.tls-no-skip-error.yml"; - - /// - /// Sample configuration file with incorrect TLS configuration in cluster section - /// - private static readonly string kubeConfigTlsSkip = "assets/kubeconfig.tls-skip.yml"; - - /// - /// The configuration file is not present. An KubeConfigException should be thrown - /// - [Fact] - public void ConfigurationFileNotFound() - { - var fi = new FileInfo("/path/to/nowhere"); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); - } - /// - /// Checks Host is loaded from the default configuration file - /// - [Fact] - public void DefaultConfigurationLoaded() - { - var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(new FileInfo(kubeConfigFileName)); - Assert.NotNull(cfg.Host); - } - - /// - /// Check if host is properly loaded, per context + /// Check if host is properly loaded, per context /// [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(kubeConfigFileName); + var fi = new FileInfo("assets/kubeconfig.yml"); var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context); Assert.Equal(host, cfg.Host); } /// - /// Checks if user-based token is loaded properly from the config file, per context + /// Checks if user-based token is loaded properly from the config file, per context /// /// /// @@ -99,7 +28,7 @@ public void ContextHost(string context, string host) [InlineData("queen-anne-context", "black-token")] public void ContextUserToken(string context, string token) { - var fi = new FileInfo(kubeConfigFileName); + var fi = new FileInfo("assets/kubeconfig.yml"); var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context); Assert.Equal(context, cfg.CurrentContext); Assert.Null(cfg.Username); @@ -107,16 +36,16 @@ public void ContextUserToken(string context, string token) } /// - /// Checks if certificate-based authentication is loaded properly from the config file, per context + /// Checks if certificate-based authentication is loaded properly from the config file, per context /// /// Context to retreive the configuration - /// 'client-certificate-data' node content + /// 'client-certificate-data' node content /// 'client-key-data' content [Theory] [InlineData("federal-context", "assets/client.crt", "assets/client.key")] - public void ContextCertificateTest(string context, string clientCert, string clientCertKey) + public void ContextCertificate(string context, string clientCert, string clientCertKey) { - var fi = new FileInfo(kubeConfigFileName); + var fi = new FileInfo("assets/kubeconfig.yml"); var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context); Assert.Equal(context, cfg.CurrentContext); Assert.Equal(cfg.ClientCertificateFilePath, clientCert); @@ -124,138 +53,211 @@ public void ContextCertificateTest(string context, string clientCert, string cli } /// - /// Checks if certificate-based authentication is loaded properly from the config file, per context + /// Checks if certificate-based authentication is loaded properly from the config file, per context /// /// Context to retreive the configuration [Theory] [InlineData("victorian-context")] public void ClientData(string context) { - var fi = new FileInfo(kubeConfigFileName); + var fi = new FileInfo("assets/kubeconfig.yml"); var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context); Assert.Equal(context, cfg.CurrentContext); Assert.NotNull(cfg.SslCaCert); - Assert.Equal(readLine("assets/client-certificate-data.txt"), cfg.ClientCertificateData); - Assert.Equal(readLine("assets/client-key-data.txt"), cfg.ClientCertificateKeyData); + Assert.Equal(File.ReadAllText("assets/client-certificate-data.txt"), cfg.ClientCertificateData); + Assert.Equal(File.ReadAllText("assets/client-key-data.txt"), cfg.ClientCertificateKeyData); } + /// + /// Checks that a KubeConfigException is thrown when no certificate-authority-data is set and user do not require tls + /// skip + /// + [Fact] + public void CheckClusterTlsCorrectness() + { + var fi = new FileInfo("assets/kubeconfig.tls-no-skip-error.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + } /// - /// Test that an Exception is thrown when initializating a KubernetClientConfiguration whose config file Context is not present + /// Checks that a KubeConfigException is thrown when no certificate-authority-data is set and user do not require tls + /// skip /// [Fact] - public void ContextNotFound() + public void CheckClusterTlsSkipCorrectness() { - var fi = new FileInfo(kubeConfigFileName); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context-not-found")); + 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); } /// - /// Test if KubeConfigException is thrown when no Contexts and we use the default context name + /// Checks that a KubeConfigException is thrown when the cluster defined in clusters and contexts do not match /// [Fact] - public void NoContexts() + public void ClusterNameMissmatch() { - var fi = new FileInfo(kubeConfigNoContexts); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + var fi = new FileInfo("assets/kubeconfig.cluster-missmatch.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// - /// Test if KubeConfigException is thrown when no Contexts are set and we specify a concrete context name + /// Checks that a KubeConfigException is thrown when the clusters section is missing /// [Fact] - public void NoContextsExplicit() + public void ClusterNotFound() { - var fi = new FileInfo(kubeConfigNoContexts); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context")); + var fi = new FileInfo("assets/kubeconfig.no-cluster.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// - /// Checks user/password authentication information is read properly + /// The configuration file is not present. An KubeConfigException should be thrown /// [Fact] - public void UserPasswordAuthentication() + public void ConfigurationFileNotFound() { - var fi = new FileInfo(kubeConfigUserPassword); - var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi); - Assert.Equal("admin", cfg.Username); - Assert.Equal("secret", cfg.Password); + var fi = new FileInfo("/path/to/nowhere"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } + /// - /// Checks that a KubeConfigException is thrown when incomplete user credentials + /// Test that an Exception is thrown when initializating a KubernetClientConfiguration whose config file Context is not + /// present + /// + [Fact] + public void ContextNotFound() + { + var fi = new FileInfo("assets/kubeconfig.yml"); + Assert.Throws(() => + KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context-not-found")); + } + + /// + /// Checks Host is loaded from the default configuration file + /// + [Fact] + public void DefaultConfigurationLoaded() + { + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(new FileInfo("assets/kubeconfig.yml")); + Assert.NotNull(cfg.Host); + } + + /// + /// Checks that a KubeConfigException is thrown when incomplete user credentials /// [Fact] public void IncompleteUserCredentials() { - var fi = new FileInfo(kubeConfigNoCredentials); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + var fi = new FileInfo("assets/kubeconfig.no-credentials.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + } + + /// + /// Test if KubeConfigException is thrown when no Contexts and we use the default context name + /// + [Fact] + public void NoContexts() + { + var fi = new FileInfo("assets/kubeconfig.no-context.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + } + + /// + /// Test if KubeConfigException is thrown when no Contexts are set and we specify a concrete context name + /// + [Fact] + public void NoContextsExplicit() + { + var fi = new FileInfo("assets/kubeconfig-no-context.yml"); + Assert.Throws(() => + KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context")); } /// - /// Checks that a KubeConfigException is thrown when the server property is not set in cluster + /// Checks that a KubeConfigException is thrown when the server property is not set in cluster /// [Fact] public void ServerNotFound() { - var fi = new FileInfo(kubeConfigNoServer); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + var fi = new FileInfo("assets/kubeconfig.no-server.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// - /// Checks that a KubeConfigException is thrown when the clusters section is missing + /// Checks user/password authentication information is read properly /// [Fact] - public void ClusterNotFound() + public void UserPasswordAuthentication() { - var fi = new FileInfo(kubeConfigNoCluster); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + var fi = new FileInfo("assets/kubeconfig.user-pass.yml"); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi); + Assert.Equal("admin", cfg.Username); + Assert.Equal("secret", cfg.Password); } /// - /// Checks that a KubeConfigException is thrown when the cluster defined in clusters and contexts do not match + /// Checks that a KubeConfigException is thrown when user cannot be found in users /// [Fact] - public void ClusterNameMissmatch() + public void UserNotFound() { - var fi = new FileInfo(kubeConfigClusterMissmatch); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + var fi = new FileInfo("assets/kubeconfig.user-not-found.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); } /// - /// Checks that a KubeConfigException is thrown when no certificate-authority-data is set and user do not require tls skip + /// Make sure that user is not a necessary field. set #issue 24 /// [Fact] - public void CheckClusterTlsCorrectness() + public void EmptyUserNotFound() { - var fi = new FileInfo(kubeConfigTlsNoSkipError); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + var fi = new FileInfo("assets/kubeconfig.no-user.yml"); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi); + + Assert.NotEmpty(cfg.Host); } /// - /// Checks that a KubeConfigException is thrown when no certificate-authority-data is set and user do not require tls skip + /// Make sure Host is replaced by masterUrl /// [Fact] - public void CheckClusterTlsSkipCorrectness() + public void OverrideByMasterUrl() + { + var fi = new FileInfo("assets/kubeconfig.yml"); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, masterUrl: "http://test.server"); + Assert.Equal("http://test.server", cfg.Host); + } + + /// + /// Make sure that http urls are loaded even if insecure-skip-tls-verify === true + /// + [Fact] + public void SmartSkipTlsVerify() { - var fi = new FileInfo(kubeConfigTlsSkip); + var fi = new FileInfo("assets/kubeconfig.tls-skip-http.yml"); var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi); - Assert.NotNull(cfg.Host); - Assert.Null(cfg.SslCaCert); - Assert.True(cfg.SkipTlsVerify); + Assert.False(cfg.SkipTlsVerify); + Assert.Equal("http://horse.org", cfg.Host); } - // /// - // /// Checks if the are pods - // /// - // [Fact] - // public void ListDefaultNamespacedPod() - // { - // var k8sClientConfig = KubernetesClientConfiguration.BuildConfigFromConfigFile(); - // IKubernetes client = new Kubernetes(k8sClientConfig); - // var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default").Result; - // var list = listTask.Body; - // Assert.NotEqual(0, list.Items.Count); - // } + /// + /// Checks config could work well when current-context is not set but masterUrl is set. #issue 24 + /// + [Fact] + public void NoCurrentContext() + { + var fi = new FileInfo("assets/kubeconfig.no-current-context.yml"); + + // failed if cannot infer any server host + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + + // survive when masterUrl is set + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, masterUrl: "http://test.server"); + Assert.Equal("http://test.server", cfg.Host); + } } } diff --git a/tests/assets/kubeconfig-no-context.yml b/tests/assets/kubeconfig.no-context.yml similarity index 100% rename from tests/assets/kubeconfig-no-context.yml rename to tests/assets/kubeconfig.no-context.yml diff --git a/tests/assets/kubeconfig.no-current-context.yml b/tests/assets/kubeconfig.no-current-context.yml new file mode 100644 index 000000000..2dd066f4f --- /dev/null +++ b/tests/assets/kubeconfig.no-current-context.yml @@ -0,0 +1,19 @@ +--- +apiVersion: v1 +clusters: +- cluster: + certificate-authority: assets/ca.crt + server: https://horse.org:4443 + name: horse-cluster +contexts: +- context: + cluster: horse-cluster + namespace: chisel-ns + user: green-user + name: federal-context +kind: Config +users: +- name: green-user + user: + password: secret + username: admin \ No newline at end of file diff --git a/tests/assets/kubeconfig.no-user.yml b/tests/assets/kubeconfig.no-user.yml new file mode 100644 index 000000000..3fdbc2c0c --- /dev/null +++ b/tests/assets/kubeconfig.no-user.yml @@ -0,0 +1,14 @@ +--- +current-context: federal-context +apiVersion: v1 +clusters: +- cluster: + certificate-authority: assets/ca.crt + server: https://horse.org:4443 + name: horse-cluster +contexts: +- context: + cluster: horse-cluster + namespace: chisel-ns + name: federal-context +kind: Config \ No newline at end of file diff --git a/tests/assets/kubeconfig.tls-skip-http.yml b/tests/assets/kubeconfig.tls-skip-http.yml new file mode 100644 index 000000000..55fd56ac7 --- /dev/null +++ b/tests/assets/kubeconfig.tls-skip-http.yml @@ -0,0 +1,19 @@ +--- +current-context: federal-context +apiVersion: v1 +clusters: +- cluster: + server: http://horse.org + name: horse-cluster +contexts: +- context: + cluster: horse-cluster + namespace: chisel-ns + user: green-user + name: federal-context +kind: Config +users: +- name: green-user + user: + password: secret + username: admin \ No newline at end of file diff --git a/tests/assets/kubeconfig.user-not-found.yml b/tests/assets/kubeconfig.user-not-found.yml new file mode 100644 index 000000000..39b79f6bf --- /dev/null +++ b/tests/assets/kubeconfig.user-not-found.yml @@ -0,0 +1,15 @@ +--- +current-context: federal-context +apiVersion: v1 +clusters: +- cluster: + certificate-authority: assets/ca.crt + server: https://horse.org:4443 + name: horse-cluster +contexts: +- context: + cluster: horse-cluster + namespace: chisel-ns + user: green-user + name: federal-context +kind: Config \ No newline at end of file