Make LoadKubeConfig public and refactor to use YAML helper methods (#133)

* Made LoadKubeConfig public and refactored to use YAML helper methods

* Addressing Code review feedback
This commit is contained in:
Bill Pratt
2018-04-18 21:52:19 -07:00
committed by Brendan Burns
parent ea62ca00e0
commit df4d5dc31a
6 changed files with 206 additions and 86 deletions

View File

@@ -11,13 +11,13 @@ namespace k8s.KubeConfigModels
/// <summary>
/// Gets or sets the path to a cert file for the certificate authority.
/// </summary>
[YamlMember(Alias = "certificate-authority")]
[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")]
[YamlMember(Alias = "certificate-authority-data", ApplyNamingConventions = false)]
public string CertificateAuthorityData { get; set; }
/// <summary>
@@ -30,7 +30,7 @@ namespace k8s.KubeConfigModels
/// 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")]
[YamlMember(Alias = "insecure-skip-tls-verify", ApplyNamingConventions = false)]
public bool SkipTlsVerify { get; set; }
/// <summary>

View File

@@ -27,7 +27,7 @@ namespace k8s.KubeConfigModels
/// <summary>
/// Gets or sets the name of the context that you would like to use by default.
/// </summary>
[YamlMember(Alias = "current-context")]
[YamlMember(Alias = "current-context", ApplyNamingConventions = false)]
public string CurrentContext { get; set; }
/// <summary>

View File

@@ -12,25 +12,25 @@ namespace k8s.KubeConfigModels
/// <summary>
/// Gets or sets PEM-encoded data from a client cert file for TLS. Overrides <see cref="ClientCertificate"/>.
/// </summary>
[YamlMember(Alias = "client-certificate-data")]
[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")]
[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")]
[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")]
[YamlMember(Alias = "client-key", ApplyNamingConventions = false)]
public string ClientKey { get; set; }
/// <summary>
@@ -48,13 +48,13 @@ namespace k8s.KubeConfigModels
/// <summary>
/// Gets or sets the groups to imperonate.
/// </summary>
[YamlMember(Alias = "as-groups")]
[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")]
[YamlMember(Alias = "as-user-extra", ApplyNamingConventions = false)]
public Dictionary<string, string> ImpersonateUserExtra { get; set; } = new Dictionary<string, string>();
/// <summary>
@@ -72,7 +72,7 @@ namespace k8s.KubeConfigModels
/// <summary>
/// Gets or sets custom authentication plugin for the kubernetes cluster.
/// </summary>
[YamlMember(Alias = "auth-provider")]
[YamlMember(Alias = "auth-provider", ApplyNamingConventions = false)]
public Dictionary<string, dynamic> AuthProvider { get; set; }
/// <summary>

View File

@@ -3,10 +3,10 @@ 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;
using YamlDotNet.Serialization;
namespace k8s
{
public partial class KubernetesClientConfiguration
@@ -28,9 +28,9 @@ namespace k8s
/// Initializes a new instance of the <see cref="KubernetesClientConfiguration" /> from config file
/// </summary>
/// <param name="masterUrl">kube api server endpoint</param>
/// <param name="kubeconfigPath">kubeconfig filepath</param>
public static KubernetesClientConfiguration BuildConfigFromConfigFile(string masterUrl = null,
string kubeconfigPath = null)
/// <param name="kubeconfigPath">Explicit file path to kubeconfig. Set to null to use the default file path</param>
public static KubernetesClientConfiguration BuildConfigFromConfigFile(string kubeconfigPath,
string currentContext = null, string masterUrl = null)
{
return BuildConfigFromConfigFile(new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation), null,
masterUrl);
@@ -55,25 +55,6 @@ namespace k8s
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(string kubeconfig,
string currentContext = null, string masterUrl = null)
{
if (string.IsNullOrWhiteSpace(kubeconfig))
{
throw new NullReferenceException(nameof(kubeconfig));
}
var k8SConfig = LoadKubeConfig(kubeconfig);
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
return k8SConfiguration;
}
/// <summary>
/// </summary>
/// <param name="kubeconfig">Fileinfo of the kubeconfig, cannot be null, whitespaced or empty</param>
@@ -94,7 +75,7 @@ namespace k8s
kubeconfig.Position = 0;
var k8SConfig = LoadKubeConfig(kubeconfig);
var k8SConfig = Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfig).GetAwaiter().GetResult();
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
return k8SConfiguration;
@@ -259,6 +240,46 @@ namespace k8s
throw new KubeConfigException(
$"User: {userDetails.Name} does not have appropriate auth credentials in kubeconfig");
}
}
/// <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>
/// <returns></returns>
public static async Task<K8SConfiguration> LoadKubeConfigAsync(string kubeconfigPath = null)
{
var fileInfo = new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation);
return await LoadKubeConfigAsync(fileInfo);
}
/// <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>
/// <returns></returns>
public static K8SConfiguration LoadKubeConfig(string kubeconfigPath = null)
{
return LoadKubeConfigAsync(kubeconfigPath).GetAwaiter().GetResult();
}
// <summary>
/// Loads Kube Config
/// </summary>
/// <param name="kubeconfig">Kube config file contents</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static async Task<K8SConfiguration> LoadKubeConfigAsync(FileInfo kubeconfig)
{
if (!kubeconfig.Exists)
{
throw new KubeConfigException($"kubeconfig file not found at {kubeconfig.FullName}");
}
using (var stream = kubeconfig.OpenRead())
{
return await Yaml.LoadFromStreamAsync<K8SConfiguration>(stream);
}
}
/// <summary>
@@ -266,46 +287,29 @@ namespace k8s
/// </summary>
/// <param name="kubeconfig">Kube config file contents</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
private static K8SConfiguration LoadKubeConfig(FileInfo kubeconfig)
{
if (!kubeconfig.Exists)
{
throw new KubeConfigException($"kubeconfig file not found at {kubeconfig.FullName}");
}
var deserializeBuilder = new DeserializerBuilder();
var deserializer = deserializeBuilder.Build();
using (var kubeConfigTextStream = kubeconfig.OpenText())
{
return deserializer.Deserialize<K8SConfiguration>(kubeConfigTextStream);
}
}
/// <summary>
/// Loads Kube Config from string
/// </summary>
/// <param name="kubeconfig">Kube config file contents</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
private static K8SConfiguration LoadKubeConfig(string kubeconfig)
{
var deserializeBuilder = new DeserializerBuilder();
var deserializer = deserializeBuilder.Build();
return deserializer.Deserialize<K8SConfiguration>(kubeconfig);
}
/// <summary>
/// Loads Kube Config from stream.
/// </summary>
/// <param name="kubeconfig">Kube config file contents</param>
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
private static K8SConfiguration LoadKubeConfig(Stream kubeconfig)
public static K8SConfiguration LoadKubeConfig(FileInfo kubeconfig)
{
using (var sr = new StreamReader(kubeconfig))
{
var strKubeConfig = sr.ReadToEnd();
return LoadKubeConfig(strKubeConfig);
}
return LoadKubeConfigAsync(kubeconfig).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();
}
}
}

View File

@@ -18,8 +18,7 @@ namespace k8s.Tests
[Fact]
public void LoadFromFiles()
{
var fi = new FileInfo(kubeConfigFileName);
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "federal-context");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigFileName, "federal-context");
// Just validate that this doesn't throw and private key is non-null
var cert = CertUtils.GeneratePfx(cfg);
@@ -32,8 +31,7 @@ namespace k8s.Tests
[Fact]
public void LoadFromInlineData()
{
var fi = new FileInfo(kubeConfigFileName);
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "victorian-context");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigFileName, "victorian-context");
// Just validate that this doesn't throw and private key is non-null
var cert = CertUtils.GeneratePfx(cfg);

View File

@@ -1,5 +1,7 @@
using System.IO;
using System.Linq;
using k8s.Exceptions;
using k8s.KubeConfigModels;
using Xunit;
namespace k8s.Tests
@@ -270,8 +272,8 @@ namespace k8s.Tests
public void DeletedConfigurationFile()
{
var assetFileInfo = new FileInfo("assets/kubeconfig.yml");
var tempFileInfo = new FileInfo(Path.GetTempFileName());
var tempFileInfo = new FileInfo(Path.GetTempFileName());
File.Copy(assetFileInfo.FullName, tempFileInfo.FullName, /* overwrite: */ true);
KubernetesClientConfiguration config;
@@ -292,9 +294,8 @@ namespace k8s.Tests
[Fact]
public void DefaultConfigurationAsStringLoaded()
{
var txt = File.ReadAllText("assets/kubeconfig.yml");
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(txt, null, null);
var filePath = "assets/kubeconfig.yml";
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null);
Assert.NotNull(cfg.Host);
}
@@ -318,10 +319,127 @@ namespace k8s.Tests
[Fact]
public void AsUserExtra()
{
var txt = File.ReadAllText("assets/kubeconfig.as-user-extra.yml");
var filePath = "assets/kubeconfig.as-user-extra.yml";
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(txt, null, null);
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null);
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.All(x => actualCreds.AuthProvider.Contains(x)));
}
}
}
}