Support relative paths in Kubernetes configuration files (#141)
* Support relative paths in Kubernetes configuration files * Filename -> FileName * Filename -> FileName * KuberentesClientConfiguration: Allow the user to opt-out of the mechanism which resolves relative paths in the configuration file. * Update unit tests * Fix test
This commit is contained in:
committed by
Brendan Burns
parent
eea4c88f8e
commit
d90289a094
@@ -52,6 +52,13 @@ namespace k8s.KubeConfigModels
|
||||
/// 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; }
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
[YamlIgnore]
|
||||
public string FileName { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,28 +28,32 @@ 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">Explicit file path to kubeconfig. Set to null to use the default file path</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)
|
||||
string currentContext = null, string masterUrl = null, bool useRelativePaths = true)
|
||||
{
|
||||
return BuildConfigFromConfigFile(new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation), null,
|
||||
masterUrl);
|
||||
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">overrider kube api server endpoint, 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)
|
||||
string currentContext = null, string masterUrl = null, bool useRelativePaths = true)
|
||||
{
|
||||
if (kubeconfig == null)
|
||||
{
|
||||
throw new NullReferenceException(nameof(kubeconfig));
|
||||
}
|
||||
|
||||
var k8SConfig = LoadKubeConfig(kubeconfig);
|
||||
var k8SConfig = LoadKubeConfig(kubeconfig, useRelativePaths);
|
||||
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
|
||||
|
||||
return k8SConfiguration;
|
||||
@@ -172,7 +176,7 @@ namespace k8s
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthority))
|
||||
{
|
||||
SslCaCert = new X509Certificate2(clusterDetails.ClusterEndpoint.CertificateAuthority);
|
||||
SslCaCert = new X509Certificate2(GetFullPath(k8SConfig, clusterDetails.ClusterEndpoint.CertificateAuthority));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,8 +234,8 @@ namespace k8s
|
||||
if (!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientCertificate) &&
|
||||
!string.IsNullOrWhiteSpace(userDetails.UserCredentials.ClientKey))
|
||||
{
|
||||
ClientCertificateFilePath = userDetails.UserCredentials.ClientCertificate;
|
||||
ClientKeyFilePath = userDetails.UserCredentials.ClientKey;
|
||||
ClientCertificateFilePath = GetFullPath(k8SConfig, userDetails.UserCredentials.ClientCertificate);
|
||||
ClientKeyFilePath = GetFullPath(k8SConfig, userDetails.UserCredentials.ClientKey);
|
||||
userCredentialsFound = true;
|
||||
}
|
||||
|
||||
@@ -245,31 +249,37 @@ namespace k8s
|
||||
/// <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)
|
||||
/// <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);
|
||||
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>
|
||||
/// <returns></returns>
|
||||
public static K8SConfiguration LoadKubeConfig(string kubeconfigPath = null)
|
||||
/// <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).GetAwaiter().GetResult();
|
||||
return LoadKubeConfigAsync(kubeconfigPath, useRelativePaths).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
// <summary>
|
||||
/// Loads Kube Config
|
||||
/// </summary>
|
||||
/// <param name="kubeconfig">Kube config file contents</param>
|
||||
/// <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)
|
||||
public static async Task<K8SConfiguration> LoadKubeConfigAsync(FileInfo kubeconfig, bool useRelativePaths = true)
|
||||
{
|
||||
if (!kubeconfig.Exists)
|
||||
{
|
||||
@@ -278,18 +288,27 @@ namespace k8s
|
||||
|
||||
using (var stream = kubeconfig.OpenRead())
|
||||
{
|
||||
return await Yaml.LoadFromStreamAsync<K8SConfiguration>(stream);
|
||||
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="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)
|
||||
public static K8SConfiguration LoadKubeConfig(FileInfo kubeconfig, bool useRelativePaths = true)
|
||||
{
|
||||
return LoadKubeConfigAsync(kubeconfig).GetAwaiter().GetResult();
|
||||
return LoadKubeConfigAsync(kubeconfig, useRelativePaths).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// <summary>
|
||||
@@ -311,5 +330,35 @@ namespace k8s
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,16 @@ namespace k8s.Tests
|
||||
public class CertUtilsTests
|
||||
{
|
||||
/// <summary>
|
||||
/// This file contains a sample kubeconfig file
|
||||
/// This file contains a sample kubeconfig file. The paths to the certificate files are relative
|
||||
/// to the current working directly.
|
||||
/// </summary>
|
||||
private static readonly string kubeConfigFileName = "assets/kubeconfig.yml";
|
||||
private static readonly string kubeConfigFileName = "assets/kubeconfig.yml";
|
||||
|
||||
/// <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.
|
||||
@@ -18,7 +25,20 @@ namespace k8s.Tests
|
||||
[Fact]
|
||||
public void LoadFromFiles()
|
||||
{
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigFileName, "federal-context");
|
||||
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]
|
||||
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);
|
||||
@@ -31,7 +51,20 @@ namespace k8s.Tests
|
||||
[Fact]
|
||||
public void LoadFromInlineData()
|
||||
{
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigFileName, "victorian-context");
|
||||
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);
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace k8s.Tests
|
||||
public void ContextHost(string context, string host)
|
||||
{
|
||||
var fi = new FileInfo("assets/kubeconfig.yml");
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context);
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context, useRelativePaths: false);
|
||||
Assert.Equal(host, cfg.Host);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace k8s.Tests
|
||||
public void ContextCertificate(string context, string clientCert, string clientCertKey)
|
||||
{
|
||||
var fi = new FileInfo("assets/kubeconfig.yml");
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context);
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, context, useRelativePaths: false);
|
||||
Assert.Equal(context, cfg.CurrentContext);
|
||||
Assert.Equal(cfg.ClientCertificateFilePath, clientCert);
|
||||
Assert.Equal(cfg.ClientKeyFilePath, clientCertKey);
|
||||
@@ -144,7 +144,7 @@ namespace k8s.Tests
|
||||
[Fact]
|
||||
public void DefaultConfigurationLoaded()
|
||||
{
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(new FileInfo("assets/kubeconfig.yml"));
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(new FileInfo("assets/kubeconfig.yml"), useRelativePaths: false);
|
||||
Assert.NotNull(cfg.Host);
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace k8s.Tests
|
||||
public void IncompleteUserCredentials()
|
||||
{
|
||||
var fi = new FileInfo("assets/kubeconfig.no-credentials.yml");
|
||||
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
|
||||
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -196,7 +196,7 @@ namespace k8s.Tests
|
||||
public void UserPasswordAuthentication()
|
||||
{
|
||||
var fi = new FileInfo("assets/kubeconfig.user-pass.yml");
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi);
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false);
|
||||
Assert.Equal("admin", cfg.Username);
|
||||
Assert.Equal("secret", cfg.Password);
|
||||
}
|
||||
@@ -208,7 +208,7 @@ namespace k8s.Tests
|
||||
public void UserNotFound()
|
||||
{
|
||||
var fi = new FileInfo("assets/kubeconfig.user-not-found.yml");
|
||||
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi));
|
||||
Assert.Throws<KubeConfigException>(() => KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -218,7 +218,7 @@ namespace k8s.Tests
|
||||
public void EmptyUserNotFound()
|
||||
{
|
||||
var fi = new FileInfo("assets/kubeconfig.no-user.yml");
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi);
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, useRelativePaths: false);
|
||||
|
||||
Assert.NotEmpty(cfg.Host);
|
||||
}
|
||||
@@ -230,7 +230,7 @@ namespace k8s.Tests
|
||||
public void OverrideByMasterUrl()
|
||||
{
|
||||
var fi = new FileInfo("assets/kubeconfig.yml");
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, masterUrl: "http://test.server");
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, masterUrl: "http://test.server", useRelativePaths: false);
|
||||
Assert.Equal("http://test.server", cfg.Host);
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ namespace k8s.Tests
|
||||
|
||||
try
|
||||
{
|
||||
config = KubernetesClientConfiguration.BuildConfigFromConfigFile(tempFileInfo);
|
||||
config = KubernetesClientConfiguration.BuildConfigFromConfigFile(tempFileInfo, useRelativePaths: false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -295,7 +295,7 @@ namespace k8s.Tests
|
||||
public void DefaultConfigurationAsStringLoaded()
|
||||
{
|
||||
var filePath = "assets/kubeconfig.yml";
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null);
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null, useRelativePaths: false);
|
||||
Assert.NotNull(cfg.Host);
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ namespace k8s.Tests
|
||||
{
|
||||
var filePath = "assets/kubeconfig.as-user-extra.yml";
|
||||
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null);
|
||||
var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(filePath, null, null, useRelativePaths: false);
|
||||
Assert.NotNull(cfg.Host);
|
||||
}
|
||||
|
||||
|
||||
53
tests/KubernetesClient.Tests/assets/kubeconfig.relative.yml
Normal file
53
tests/KubernetesClient.Tests/assets/kubeconfig.relative.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
# Sample file based on https://kubernetes.io/docs/tasks/access-application-cluster/authenticate-across-clusters-kubeconfig/
|
||||
# WARNING: File includes minor fixes
|
||||
---
|
||||
current-context: federal-context
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: http://cow.org:8080
|
||||
name: cow-cluster
|
||||
- cluster:
|
||||
certificate-authority: ca.crt
|
||||
server: https://horse.org:4443
|
||||
name: horse-cluster
|
||||
- cluster:
|
||||
insecure-skip-tls-verify: true
|
||||
server: https://pig.org:443
|
||||
name: pig-cluster
|
||||
- cluster:
|
||||
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURERENDQWZTZ0F3SUJBZ0lSQUo5ZCtLeThkTDJVSzRjdXplMmo2WnN3RFFZSktvWklodmNOQVFFTEJRQXcKTHpFdE1Dc0dBMVVFQXhNa1lXRTBZVFV3T0RZdE0yVm1aaTAwWWpCa0xUbGxORGt0WmpNeVpXWXpabUpqWWpNNApNQjRYRFRFM01ESXlOakExTURRek5Gb1hEVEl5TURJeU5UQTFNRFF6TkZvd0x6RXRNQ3NHQTFVRUF4TWtZV0UwCllUVXdPRFl0TTJWbVppMDBZakJrTFRsbE5Ea3Raak15WldZelptSmpZak00TUlJQklqQU5CZ2txaGtpRzl3MEIKQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBM2dkandhdHNsdCsvQVpqV3hmbkNQeGZqMzNHUUxlOU00VU42VmEwRQpKd0FYL2R3L1ZVa0dvVjlDc3NKRUZMdEdTUnM2K2h0RTEvOUN3ak1USDh2WExKcURHTE9KdFQ5dW9sR2c2Q2k1ClBKNDNKelVLWmJlYVE4Z3hhZndzQjdQU05vTTJOYzROVm9lZzBVTUw0bndGeEhXeTNYWHlFZ0QxTWxTUnVrb3oKTTNoRUVxUjJNVFdrNm9KK3VJNFF4WVZWMnZuWXdXaEJwUDlDR3RWUTlyUW9MVFowcmFpOCtDYURBMVltTWRhbQpRYUVPdURlSFRqU2FYM2dyR0FBVVFWNWl6MC9qVVBuK3lJNm1iV0trbzFzNytPY1dZR2F1aDFaMzFYSjJsc0RTCnU4a3F0d215UEcyUVl2aUQ4YjNOWFAyY0dRK2EwZlpRZnBrbTF0U3IxQnhhaXdJREFRQUJveU13SVRBT0JnTlYKSFE4QkFmOEVCQU1DQWdRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQQpuVzFXVXlLbVJ0TlNzU1VzVFBSUnhFRzhhek9kdjdYeUhRL0R5VWNqWm9rUEJVVHY4VjdvNG96RHgyVHV6UEdYCmZ2YlMvT2g0VDd6ZlYxdjJadmU3dTBxelNiRTl5OGpsaDNxYXJEcEd5ZmlTamwycmhIOFBmay9sZGR0VFpVL04KSkVtYW5ReGl6R20xV2pCSklRSE5LZENneVIwN3A1c0MwNnR3K25YUytla1MxMlBUTG45WjBuRDBKVDdQSzRXQgpQc3ZXeDVXN0w5dnJIdVN5SGRSTkt5eEEvbWI1WHdXMDBkZUpmaHZub0p3ZWRYNDVKZVRiME5MczUzaURqVEU1CnRpdU03Z1RVSjlCcGZTL0gvYSt2SmovVWQ2bHM0QndrWmpUNHNhOTA1bnNzdnRqamlwZ1N5a0QzVkxCQ3VueTkKd1NnbE1vSnZNWmg0bC9FVFJPeFE3Zz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
||||
server: https://llama.org:443
|
||||
name: llama-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: horse-cluster
|
||||
namespace: chisel-ns
|
||||
user: green-user
|
||||
name: federal-context
|
||||
- context:
|
||||
cluster: pig-cluster
|
||||
namespace: saw-ns
|
||||
user: black-user
|
||||
name: queen-anne-context
|
||||
- context:
|
||||
cluster: llama-cluster
|
||||
namespace: saw-ns
|
||||
user: red-user
|
||||
name: victorian-context
|
||||
kind: Config
|
||||
users:
|
||||
- name: blue-user
|
||||
user:
|
||||
token: blue-token
|
||||
- name: green-user
|
||||
user:
|
||||
client-certificate: client.crt
|
||||
client-key: client.key
|
||||
- name: black-user
|
||||
user:
|
||||
token: black-token
|
||||
- name: red-user
|
||||
user:
|
||||
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlEUkRDQ0FpeWdBd0lCQWdJSUxzVmNxZ0pmOWdZd0RRWUpLb1pJaHZjTkFRRUxCUUF3RXpFUk1BOEdBMVVFQXd3SVlXTnphemh6DQpZMkV3SGhjTk1UY3dPVEl3TURBd01EQXdXaGNOTVRrd09USXdNREF3TURBd1dqQVZNUk13RVFZRFZRUUREQXByZFdKbFkyOXVabWxuDQpNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXJQVjB0Rmp3ZkxVL0p6M3RaUXRRblNSRG5aMGpLTGtjDQpRcnJ2a2pwbEhpVkFVOVNkRG9nT0VneUVWQjJoMU0xSWNYSVZYM2lCTGFHS04rSTl2dGZQSFQ2QmViTzVKNzFaOHRGMytJU1pNRzBLDQpzSWwzQS94VEJWKzlwRHVEU3pXTGNVTVoxbnhWV1Bma0paRkVTQTNCakxFT1NGWWYySUF4V0g2cjJDRHp0SDMySzVLbUNaLzR1NFFsDQpCT204YWoxVkFUbUZvK3BJaTNmUXVKcm1TblBSRGRlSW5HVGYwaEYyQ3oybzYrNU1TSzAxbURuaERyZVF5eTJHNXZXRnlWUjNxZ3pGDQpOMHFyaUEvSFRGcklSazdtbFZtaEpPM3A5cU0wRGdJc29XdHg0Ukdqc1lycTBmOTBnREhna1YvS0E3UEhZQlhheVVnQjhLcTUwS2xHDQpxczRyYVFJREFRQUJvNEdaTUlHV01FSUdBMVVkSXdRN01EbUFGS0FXL3JnbWJ4YUtWcUZoaFl4bEl5TEhQbEkyb1Jla0ZUQVRNUkV3DQpEd1lEVlFRRERBaGhZM05yT0hOallZSUlSbWp6U3h0U2d5TXdIUVlEVlIwT0JCWUVGSk5mZDNaZlJZZnlQdGJZbE1NUDYvKzcrU3dIDQpNQXdHQTFVZEV3RUIvd1FDTUFBd0RnWURWUjBQQVFIL0JBUURBZ1dnTUJNR0ExVWRKUVFNTUFvR0NDc0dBUVVGQndNQ01BMEdDU3FHDQpTSWIzRFFFQkN3VUFBNElCQVFCRVd0U1JMU0lTODhFUWpRdWp4bitQdmQ0OVNxSkdTUmRFNnlraUkwcXZ5RmY1c1lnV2FlYTdaME43DQpuK3k3SmlQQXQ1MEFkdGdvYTF1bWV4bG9VZE02WDJQYTB4RUpDaklGOFArcjhPOU41U0N5NVRXYTd0VTFrWkdoZHJFOERMS2RJbXk4DQpqblhnUlBjUHVlVnRkam50TFdUMGpETDI5YVg3ZUhLcllTMWQ2dkUyWXE3djZVNFNKN2JLU0NGbFNjS1h0VjhxUFVteXFlbXd5WHNBDQpVdEtBelRKS0l0UGw3eG5icXduVm5tY3Q0a0Q1VHZBUEFTdmZGTkR0eEU0WTU1ZHNYTERSVEc3NU5VK044bi94ZnBzcnlBZkQrUGZ6DQpNR05OWVhLb0hwbm93R1Z4V3UxRHZNY2kxQTl4STF6VnFsS2FpVTZSNm9qWmx6Q2xsVU54QUhzZA0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ0K
|
||||
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ0KTUlJRW93SUJBQUtDQVFFQXJQVjB0Rmp3ZkxVL0p6M3RaUXRRblNSRG5aMGpLTGtjUXJydmtqcGxIaVZBVTlTZA0KRG9nT0VneUVWQjJoMU0xSWNYSVZYM2lCTGFHS04rSTl2dGZQSFQ2QmViTzVKNzFaOHRGMytJU1pNRzBLc0lsMw0KQS94VEJWKzlwRHVEU3pXTGNVTVoxbnhWV1Bma0paRkVTQTNCakxFT1NGWWYySUF4V0g2cjJDRHp0SDMySzVLbQ0KQ1ovNHU0UWxCT204YWoxVkFUbUZvK3BJaTNmUXVKcm1TblBSRGRlSW5HVGYwaEYyQ3oybzYrNU1TSzAxbURuaA0KRHJlUXl5Mkc1dldGeVZSM3FnekZOMHFyaUEvSFRGcklSazdtbFZtaEpPM3A5cU0wRGdJc29XdHg0Ukdqc1lycQ0KMGY5MGdESGdrVi9LQTdQSFlCWGF5VWdCOEtxNTBLbEdxczRyYVFJREFRQUJBb0lCQUJpaUpPc0N0ODJyS3NGMg0KQ25lWHN2V09rcXJDRkozYUwzSTVtYUZqKzc3ZFkxb05NQWsveTNFNm95WXZ5anE2dWhTZVFQa0YrcS83RCtxQg0KcUhXajJ2VzVUMHQ4RTJUYmpSSU9UMTN2MUxtVzdpelNoMGJrQ3hiNjJkR29RRHpYOVhJK01sSGdCMi9TYm9ZRA0KT0l4aW1TeG1remd1Ty83ajB5TmRkekFqRVZLbFdoYkZ6ajBFV01lazJ4L0VtWXBKSGlYdktZTjA3dXlxcTVMRA0KRlRNSjN1T2dsWis3U1E3eGtQcHl1OW1yMWxVZzVPeXdTV0dRVWR4Mk9IcTZXMlVHNFU4QTNXTFVwSFlLWEhqRQ0KVmg5UVNzdnNvUmptRWNaRllSRlpsRk9raVVCRC9HWm9Ya1doc3BCYU5mODRmUVdJenkzaFpyZ0MwWFc2MmszMQ0KRklVczhMa0NnWUVBMVBTRjR4MitYSnVqU2N1RXBhZWhLS2E3dmt6TXo2SlVlWHFJQlFRbmV1TE5QaVJlUThCWQ0KTVBTajNMV3ZZVzNEenNFQXBQVmMvSDNXZ0Q3V2s1bW1Dbm56elo0eDZ0eE1zaW5vVzZtNk8wWUd4b040YmZ1bg0KblliVFRucFRyRTEvS1VSZUJ6c2JaWmR1SGxwWUtocldMdmJNOW95UEJPbVNvcWk2MEdLQjNhOENnWUVBeit0TQ0KNlhYdVNWbnNUTExLNmx3U1FxWmZTNkVaa1RpOXZ2eEpaN2R1V1BxNm5CVHZtcjhZL2F0Q3l0UlhZY0FjRmF0Yw0KMVNURmdRYjJCUW82K1FGRW12NVhSUThqMmphODBMT2ZET2NCVkpTNmphOC8rK09IWDBWWGFjYUhDMkh6Y01MeA0KMlNyVXNhaEtkL1dUbW5xQmJDQTBUaDY3Q0UvOFIyZVQvdmc2Sm1jQ2dZQXV5YmFzNnJrTFljcWppUXFRMXQ2cQ0KcnM5ckJUYXVtK3pSYitGNHNLdjM3T0xKTjNaYWptVVNCSDRJSFFiMmNnWm1ZN00vaXdVdUdIdkxXNE1MbE9PTg0KUTdRVVJpQ1RpR2wxYjQyMHJmclQwUlBtQTdhdSsyNmRScVVnaGZIaVZuaU0yWStMS1NwZ3pMK04vYTJIT3JRNg0KUjFGTERpRFNKSHRxTDRZMENLQ2Qwd0tCZ0NGRWJ0dno2SnFIN3MwZTFtVEZNbzdEZS8vbjJPVnBoTUtvTHo2UA0KRlBMYnV6djZCWlJtK3lLcllsWjl2elYrdlgraUdZcHBCY2p0U2pQb1BTTldWcG5PRkR5U2ZaUU9xZ3Rpa2hKSQ0KYStnU20vN0xpWnROL256NTVWQ2hXVDR5Ly9hTTJwRjZ6dWxXR2dRem9OaFl2WmlGVnBraFJaL0EzSWE0UmUvSA0KMjlZRkFvR0JBSU81QVV6V3V1SHN5Wi9MeXMyZTg5d0w4OU5hVE1OVFcwQVlEZjhVQndMeWhyNEsrUmVsOHdiNw0KOXZ2L04wVTE0WURNT0c2S0lGQ05nZjRKZWJDOGIyOGp4cXFmTzV4clFRZWRsemU2TFFmMUhzUDJ5WVlOVkRjeg0KUWZ6bG9EOE96bmp5Y3pJY0doMkszU0cvWnJmWWtXWUxlUkVZRGpiU0VUN1cybERBa1FsVA0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0NCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
Reference in New Issue
Block a user