diff --git a/src/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClientConfiguration.ConfigFile.cs index 6ec531f..559d07a 100644 --- a/src/KubernetesClientConfiguration.ConfigFile.cs +++ b/src/KubernetesClientConfiguration.ConfigFile.cs @@ -51,12 +51,25 @@ namespace k8s 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 @@ namespace k8s /// /// 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 62d24e7..e996c05 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 @@ namespace k8s.Tests [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 @@ namespace k8s.Tests } /// - /// 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 @@ namespace k8s.Tests } /// - /// 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); - } - - - /// - /// 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(kubeConfigFileName); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context-not-found")); + Assert.Equal(File.ReadAllText("assets/client-certificate-data.txt"), cfg.ClientCertificateData); + Assert.Equal(File.ReadAllText("assets/client-key-data.txt"), cfg.ClientCertificateKeyData); } /// - /// Test if KubeConfigException is thrown when no Contexts and we use the default context name - /// - [Fact] - public void NoContexts() - { - var fi = new FileInfo(kubeConfigNoContexts); - 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(kubeConfigNoContexts); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "context")); - } - - /// - /// Checks user/password authentication information is read properly - /// - [Fact] - public void UserPasswordAuthentication() - { - var fi = new FileInfo(kubeConfigUserPassword); - var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi); - Assert.Equal("admin", cfg.Username); - Assert.Equal("secret", cfg.Password); - } - - /// - /// Checks that a KubeConfigException is thrown when incomplete user credentials - /// - [Fact] - public void IncompleteUserCredentials() - { - var fi = new FileInfo(kubeConfigNoCredentials); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); - } - - /// - /// 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)); - } - - /// - /// Checks that a KubeConfigException is thrown when the clusters section is missing - /// - [Fact] - public void ClusterNotFound() - { - var fi = new FileInfo(kubeConfigNoCluster); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); - } - - /// - /// Checks that a KubeConfigException is thrown when the cluster defined in clusters and contexts do not match - /// - [Fact] - public void ClusterNameMissmatch() - { - var fi = new FileInfo(kubeConfigClusterMissmatch); - 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 + /// 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(kubeConfigTlsNoSkipError); - Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + var fi = new FileInfo("assets/kubeconfig.tls-no-skip-error.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 + /// Checks that a KubeConfigException is thrown when no certificate-authority-data is set and user do not require tls + /// skip /// [Fact] public void CheckClusterTlsSkipCorrectness() { - var fi = new FileInfo(kubeConfigTlsSkip); + 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); } - // /// - // /// 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 that a KubeConfigException is thrown when the cluster defined in clusters and contexts do not match + /// + [Fact] + public void ClusterNameMissmatch() + { + var fi = new FileInfo("assets/kubeconfig.cluster-missmatch.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + } + + /// + /// Checks that a KubeConfigException is thrown when the clusters section is missing + /// + [Fact] + public void ClusterNotFound() + { + var fi = new FileInfo("assets/kubeconfig.no-cluster.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + } + + /// + /// 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)); + } + + + /// + /// 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("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 + /// + [Fact] + public void ServerNotFound() + { + var fi = new FileInfo("assets/kubeconfig.no-server.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + } + + /// + /// Checks user/password authentication information is read properly + /// + [Fact] + public void UserPasswordAuthentication() + { + 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 user cannot be found in users + /// + [Fact] + public void UserNotFound() + { + var fi = new FileInfo("assets/kubeconfig.user-not-found.yml"); + Assert.Throws(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi)); + } + + /// + /// Make sure that user is not a necessary field. set #issue 24 + /// + [Fact] + public void EmptyUserNotFound() + { + var fi = new FileInfo("assets/kubeconfig.no-user.yml"); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi); + + Assert.NotEmpty(cfg.Host); + } + + /// + /// Make sure Host is replaced by masterUrl + /// + [Fact] + 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("assets/kubeconfig.tls-skip-http.yml"); + var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi); + Assert.False(cfg.SkipTlsVerify); + Assert.Equal("http://horse.org", cfg.Host); + } + + /// + /// 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 0000000..2dd066f --- /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 0000000..3fdbc2c --- /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 0000000..55fd56a --- /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 0000000..39b79f6 --- /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