Skip to content

Commit f9d55fc

Browse files
committed
Merge branch 'master' into style_fix0
2 parents 8e81532 + 0d68823 commit f9d55fc

19 files changed

+874
-233
lines changed

examples/metrics/Program.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using k8s;
2+
using System;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
6+
namespace metrics
7+
{
8+
internal class Program
9+
{
10+
private static async Task NodesMetrics(IKubernetes client)
11+
{
12+
var nodesMetrics = await client.GetKubernetesNodesMetricsAsync().ConfigureAwait(false);
13+
14+
foreach (var item in nodesMetrics.Items)
15+
{
16+
Console.WriteLine(item.Metadata.Name);
17+
18+
foreach (var metric in item.Usage)
19+
{
20+
Console.WriteLine($"{metric.Key}: {metric.Value}");
21+
}
22+
}
23+
}
24+
25+
private static async Task PodsMetrics(IKubernetes client)
26+
{
27+
var podsMetrics = await client.GetKubernetesPodsMetricsAsync().ConfigureAwait(false);
28+
29+
if (!podsMetrics.Items.Any())
30+
{
31+
Console.WriteLine("Empty");
32+
}
33+
34+
foreach (var item in podsMetrics.Items)
35+
{
36+
foreach (var container in item.Containers)
37+
{
38+
Console.WriteLine(container.Name);
39+
40+
foreach (var metric in container.Usage)
41+
{
42+
Console.WriteLine($"{metric.Key}: {metric.Value}");
43+
}
44+
}
45+
46+
Console.Write(Environment.NewLine);
47+
}
48+
}
49+
50+
private static async Task Main(string[] args)
51+
{
52+
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
53+
var client = new Kubernetes(config);
54+
55+
await NodesMetrics(client).ConfigureAwait(false);
56+
Console.WriteLine(Environment.NewLine);
57+
await PodsMetrics(client).ConfigureAwait(false);
58+
}
59+
}
60+
}

examples/metrics/metrics.csproj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp3.1</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<ProjectReference Include="..\..\src\KubernetesClient\KubernetesClient.csproj" />
10+
</ItemGroup>
11+
12+
13+
</Project>

kubernetes-client.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "patch", "examples\patch\pat
3535
EndProject
3636
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "httpClientFactory", "examples\httpClientFactory\httpClientFactory.csproj", "{A07314A0-02E8-4F36-B233-726D59D28F08}"
3737
EndProject
38+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "metrics", "examples\metrics\metrics.csproj", "{B9647AD4-F6B0-406F-8B79-6781E31600EC}"
39+
EndProject
3840
Global
3941
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4042
Debug|Any CPU = Debug|Any CPU
@@ -189,6 +191,18 @@ Global
189191
{A07314A0-02E8-4F36-B233-726D59D28F08}.Release|x64.Build.0 = Release|Any CPU
190192
{A07314A0-02E8-4F36-B233-726D59D28F08}.Release|x86.ActiveCfg = Release|Any CPU
191193
{A07314A0-02E8-4F36-B233-726D59D28F08}.Release|x86.Build.0 = Release|Any CPU
194+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
195+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
196+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Debug|x64.ActiveCfg = Debug|Any CPU
197+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Debug|x64.Build.0 = Debug|Any CPU
198+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Debug|x86.ActiveCfg = Debug|Any CPU
199+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Debug|x86.Build.0 = Debug|Any CPU
200+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
201+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Release|Any CPU.Build.0 = Release|Any CPU
202+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Release|x64.ActiveCfg = Release|Any CPU
203+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Release|x64.Build.0 = Release|Any CPU
204+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Release|x86.ActiveCfg = Release|Any CPU
205+
{B9647AD4-F6B0-406F-8B79-6781E31600EC}.Release|x86.Build.0 = Release|Any CPU
192206
EndGlobalSection
193207
GlobalSection(SolutionProperties) = preSolution
194208
HideSolutionNode = FALSE
@@ -206,6 +220,7 @@ Global
206220
{542DC30E-FDF7-4A35-B026-6C21F435E8B1} = {879F8787-C3BB-43F3-A92D-6D4C7D3A5285}
207221
{04DE2C84-117D-4E21-8B45-B7AE627697BD} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40}
208222
{A07314A0-02E8-4F36-B233-726D59D28F08} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40}
223+
{B9647AD4-F6B0-406F-8B79-6781E31600EC} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40}
209224
EndGlobalSection
210225
GlobalSection(ExtensibilityGlobals) = postSolution
211226
SolutionGuid = {049A763A-C891-4E8D-80CF-89DD3E22ADC7}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Newtonsoft.Json;
2+
using System.Collections.Generic;
3+
4+
namespace k8s.Models
5+
{
6+
/// <summary>
7+
/// Describes the resource usage metrics of a container pull from metrics server API.
8+
/// </summary>
9+
public class ContainerMetrics
10+
{
11+
/// <summary>
12+
/// Defines container name corresponding to the one from pod.spec.containers.
13+
/// </summary>
14+
[JsonProperty(PropertyName = "name")]
15+
public string Name { get; set; }
16+
17+
/// <summary>
18+
/// The resource usage.
19+
/// </summary>
20+
[JsonProperty(PropertyName = "usage")]
21+
public IDictionary<string, ResourceQuantity> Usage { get; set; }
22+
}
23+
}

src/KubernetesClient/KubeConfigModels/ExternalExecution.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class ExternalExecution
1717
/// Environment variables to set when executing the plugin. Optional.
1818
/// </summary>
1919
[YamlMember(Alias = "env")]
20-
public IDictionary<string, string> EnvironmentVariables { get; set; }
20+
public IList<Dictionary<string, string>> EnvironmentVariables { get; set; }
2121

2222
/// <summary>
2323
/// Arguments to pass when executing the plugin. Optional.

src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3-
#if NETSTANDARD2_0
43
using Newtonsoft.Json;
54
using System.Diagnostics;
6-
#endif
75
using System.IO;
86
using System.Linq;
97
using System.Runtime.InteropServices;
@@ -378,7 +376,6 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext)
378376
}
379377
}
380378

381-
#if NETSTANDARD2_0
382379
if (userDetails.UserCredentials.ExternalExecution != null)
383380
{
384381
if (string.IsNullOrWhiteSpace(userDetails.UserCredentials.ExternalExecution.Command))
@@ -392,12 +389,16 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext)
392389
throw new KubeConfigException("External command execution missing ApiVersion key");
393390
}
394391

395-
var token = ExecuteExternalCommand(userDetails.UserCredentials.ExternalExecution);
396-
AccessToken = token;
392+
var (accessToken, clientCertificateData, clientCertificateKeyData) = ExecuteExternalCommand(userDetails.UserCredentials.ExternalExecution);
393+
AccessToken = accessToken;
394+
// When reading ClientCertificateData from a config file it will be base64 encoded, and code later in the system (see CertUtils.GeneratePfx)
395+
// expects ClientCertificateData and ClientCertificateKeyData to be base64 encoded because of this. However the string returned by external
396+
// auth providers is the raw certificate and key PEM text, so we need to take that and base64 encoded it here so it can be decoded later.
397+
ClientCertificateData = clientCertificateData == null ? null : Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(clientCertificateData));
398+
ClientCertificateKeyData = clientCertificateKeyData == null ? null : Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(clientCertificateKeyData));
397399

398400
userCredentialsFound = true;
399401
}
400-
#endif
401402

402403
if (!userCredentialsFound)
403404
{
@@ -411,17 +412,7 @@ public static string RenewAzureToken(string tenantId, string clientId, string ap
411412
throw new KubeConfigException("Refresh not supported.");
412413
}
413414

414-
#if NETSTANDARD2_0
415-
/// <summary>
416-
/// Implementation of the proposal for out-of-tree client
417-
/// authentication providers as described here --
418-
/// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/kubectl-exec-plugins.md
419-
/// Took inspiration from python exec_provider.py --
420-
/// https://github.com/kubernetes-client/python-base/blob/master/config/exec_provider.py
421-
/// </summary>
422-
/// <param name="config">The external command execution configuration</param>
423-
/// <returns>The token received from the external command execution</returns>
424-
public static string ExecuteExternalCommand(ExternalExecution config)
415+
public static Process CreateRunnableExternalProcess(ExternalExecution config)
425416
{
426417
var execInfo = new Dictionary<string, dynamic>
427418
{
@@ -432,15 +423,22 @@ public static string ExecuteExternalCommand(ExternalExecution config)
432423

433424
var process = new Process();
434425

435-
process.StartInfo.Environment.Add("KUBERNETES_EXEC_INFO",
436-
JsonConvert.SerializeObject(execInfo));
437-
426+
process.StartInfo.EnvironmentVariables.Add("KUBERNETES_EXEC_INFO", JsonConvert.SerializeObject(execInfo));
438427
if (config.EnvironmentVariables != null)
439428
{
440-
foreach (var configEnvironmentVariableKey in config.EnvironmentVariables.Keys)
429+
foreach (var configEnvironmentVariable in config.EnvironmentVariables)
441430
{
442-
process.StartInfo.Environment.Add(key: configEnvironmentVariableKey,
443-
value: config.EnvironmentVariables[configEnvironmentVariableKey]);
431+
if (configEnvironmentVariable.ContainsKey("name") && configEnvironmentVariable.ContainsKey("value"))
432+
{
433+
process.StartInfo.EnvironmentVariables.Add(
434+
configEnvironmentVariable["name"],
435+
configEnvironmentVariable["value"]);
436+
}
437+
else
438+
{
439+
var badVariable = string.Join(",", configEnvironmentVariable.Select(x => $"{x.Key}={x.Value}"));
440+
throw new KubeConfigException($"Invalid environment variable defined: {badVariable}");
441+
}
444442
}
445443
}
446444

@@ -454,6 +452,24 @@ public static string ExecuteExternalCommand(ExternalExecution config)
454452
process.StartInfo.RedirectStandardError = true;
455453
process.StartInfo.UseShellExecute = false;
456454

455+
return process;
456+
}
457+
458+
/// <summary>
459+
/// Implementation of the proposal for out-of-tree client
460+
/// authentication providers as described here --
461+
/// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/kubectl-exec-plugins.md
462+
/// Took inspiration from python exec_provider.py --
463+
/// https://github.com/kubernetes-client/python-base/blob/master/config/exec_provider.py
464+
/// </summary>
465+
/// <param name="config">The external command execution configuration</param>
466+
/// <returns>
467+
/// The token, client certificate data, and the client key data received from the external command execution
468+
/// </returns>
469+
public static (string, string, string) ExecuteExternalCommand(ExternalExecution config)
470+
{
471+
var process = CreateRunnableExternalProcess(config);
472+
457473
try
458474
{
459475
process.Start();
@@ -482,7 +498,23 @@ public static string ExecuteExternalCommand(ExternalExecution config)
482498
$"external exec failed because api version {responseObject.ApiVersion} does not match {config.ApiVersion}");
483499
}
484500

485-
return responseObject.Status["token"];
501+
if (responseObject.Status.ContainsKey("token"))
502+
{
503+
return (responseObject.Status["token"], null, null);
504+
}
505+
else if (responseObject.Status.ContainsKey("clientCertificateData"))
506+
{
507+
if (!responseObject.Status.ContainsKey("clientKeyData"))
508+
{
509+
throw new KubeConfigException($"external exec failed missing clientKeyData field in plugin output");
510+
}
511+
512+
return (null, responseObject.Status["clientCertificateData"], responseObject.Status["clientKeyData"]);
513+
}
514+
else
515+
{
516+
throw new KubeConfigException($"external exec failed missing token or clientCertificateData field in plugin output");
517+
}
486518
}
487519
catch (JsonSerializationException ex)
488520
{
@@ -493,7 +525,6 @@ public static string ExecuteExternalCommand(ExternalExecution config)
493525
throw new KubeConfigException($"external exec failed due to uncaught exception: {ex}");
494526
}
495527
}
496-
#endif
497528

498529
/// <summary>
499530
/// Loads entire Kube Config from default or explicit file path
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using k8s.Models;
2+
using Newtonsoft.Json.Linq;
3+
using System.Threading.Tasks;
4+
5+
namespace k8s
6+
{
7+
/// <summary>
8+
/// Extension methods for Kubernetes metrics.
9+
/// </summary>
10+
public static class KubernetesMetricsExtensions
11+
{
12+
/// <summary>
13+
/// Get nodes metrics pull from metrics server API.
14+
/// </summary>
15+
public static async Task<NodeMetricsList> GetKubernetesNodesMetricsAsync(this IKubernetes operations)
16+
{
17+
JObject customObject = (JObject)await operations.GetClusterCustomObjectAsync("metrics.k8s.io", "v1beta1", "nodes", string.Empty).ConfigureAwait(false);
18+
return customObject.ToObject<NodeMetricsList>();
19+
}
20+
21+
/// <summary>
22+
/// Get pods metrics pull from metrics server API.
23+
/// </summary>
24+
public static async Task<PodMetricsList> GetKubernetesPodsMetricsAsync(this IKubernetes operations)
25+
{
26+
JObject customObject = (JObject)await operations.GetClusterCustomObjectAsync("metrics.k8s.io", "v1beta1", "pods", string.Empty).ConfigureAwait(false);
27+
return customObject.ToObject<PodMetricsList>();
28+
}
29+
30+
/// <summary>
31+
/// Get pods metrics by namespace pull from metrics server API.
32+
/// </summary>
33+
public static async Task<PodMetricsList> GetKubernetesPodsMetricsByNamespaceAsync(this IKubernetes operations, string namespaceParameter)
34+
{
35+
JObject customObject = (JObject)await operations.GetNamespacedCustomObjectAsync("metrics.k8s.io", "v1beta1", namespaceParameter, "pods", string.Empty).ConfigureAwait(false);
36+
return customObject.ToObject<PodMetricsList>();
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)