Skip to content

Instantly share code, notes, and snippets.

@tintoy
Last active August 25, 2018 06:19
Show Gist options
  • Save tintoy/49b7c400fb89325c3a914667af4427b5 to your computer and use it in GitHub Desktop.
Save tintoy/49b7c400fb89325c3a914667af4427b5 to your computer and use it in GitHub Desktop.
Discover available Kubernetes APIs
using HTTPlease;
using KubeClient;
using KubeClient.Models;
using KubeClient.ResourceClients;
using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace K8sApiExplorer
{
/// <summary>
/// Discover available Kubernetes APIs.
/// </summary>
static class Program
{
/// <summary>
/// The main program entry-point.
/// </summary>
static async Task Main()
{
ConfigureLogging();
try
{
Log.Information("Scanning K8s API...");
var pathsByKind = new Dictionary<string, List<string>>();
using (KubeApiClient client = KubeApiClient.Create("http://127.0.0.1:8001/"))
{
ApiGroupClientV1 apiGroupClient = client.ResourceClient<ApiGroupClientV1>(
kubeClient => new ApiGroupClientV1(kubeClient)
);
ApiResourceClientV1 apiClient = client.ResourceClient<ApiResourceClientV1>(
kubeClient => new ApiResourceClientV1(kubeClient)
);
var apiGroupPrefixes = new string[] { "api", "apis" };
foreach (string apiGroupPrefix in apiGroupPrefixes)
{
APIGroupListV1 apiGroups = await apiGroupClient.List(apiGroupPrefix);
// Special case for (old-style) v1 APIs ("/api/v1", rather than "/apis/{groupName}/{groupVersion}").
if (apiGroupPrefix == "api")
{
apiGroups.Groups.Add(new APIGroupV1
{
Name = "Core",
PreferredVersion = new GroupVersionForDiscoveryV1
{
GroupVersion = "v1"
}
});
}
Log.Information("Found {ApiGroupCount} API groups with prefix {ApiGroupPrefix}:",
apiGroups.Groups.Count,
apiGroupPrefix
);
foreach (APIGroupV1 apiGroup in apiGroups.Groups)
{
Log.Information(" API Group {ApiGroupName}: {ApiPreferredVersion}",
apiGroup.Name,
apiGroup.PreferredVersion.GroupVersion
);
APIResourceListV1 apis = await apiClient.List(apiGroupPrefix, apiGroup.PreferredVersion.GroupVersion);
Log.Information(" Found {ApiCount} resource types in API group {GroupVersion}:",
apis.Resources.Count,
apiGroup.PreferredVersion.GroupVersion
);
foreach (var apisForKind in apis.Resources.GroupBy(api => api.Kind))
{
Log.Information(" Resource {ApiKind}:",
apisForKind.Key
);
foreach (APIResourceV1 api in apisForKind.OrderBy(api => api.Kind))
{
var kindAndVersion = $"{apiGroup.PreferredVersion.GroupVersion}/{api.Kind}";
List<string> paths;
if (!pathsByKind.TryGetValue(kindAndVersion, out paths))
{
paths = new List<string>();
pathsByKind.Add(kindAndVersion, paths);
}
string apiPath = $"{apiGroupPrefix}/{apiGroup.PreferredVersion.GroupVersion}/{api.Name}";
Log.Information(" Path: {ApiPath}", apiPath);
paths.Add(apiPath);
if (api.Kind == "Pod")
Log.Information("API: {@Api}", api);
if (api.Verbs.Count > 0)
{
Log.Information(" Verbs:");
foreach (string verb in api.Verbs)
Log.Information(" {ApiVerb}", verb);
}
}
}
}
}
}
Log.Information("PathsByKind: {PathsByKind}",
JsonConvert.SerializeObject(pathsByKind, Formatting.Indented)
);
}
catch (Exception unexpectedError)
{
Log.Error(unexpectedError, "Unexpected error.");
}
}
/// <summary>
/// Configure the global application logger.
/// </summary>
static void ConfigureLogging()
{
var loggerConfiguration = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.LiterateConsole(
outputTemplate: "[{Level:u3}] {Message:l}{NewLine}{Exception}"
);
Log.Logger = loggerConfiguration.CreateLogger();
}
/// <summary>
/// Global initialisation.
/// </summary>
static Program()
{
SynchronizationContext.SetSynchronizationContext(
new SynchronizationContext()
);
}
class ApiGroupClientV1
: KubeResourceClient
{
public ApiGroupClientV1(IKubeApiClient client)
: base(client)
{
}
public async Task<APIGroupListV1> List(string prefix, CancellationToken cancellationToken = default)
{
if (String.IsNullOrWhiteSpace(prefix))
throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'prefix'.", nameof(prefix));
HttpRequest request = KubeRequest.Create("{Prefix}")
.WithTemplateParameter("Prefix", prefix);
using (HttpResponseMessage responseMessage = await Http.GetAsync(request, cancellationToken).ConfigureAwait(false))
{
if (responseMessage.IsSuccessStatusCode)
return await responseMessage.ReadContentAsAsync<APIGroupListV1>().ConfigureAwait(false);
throw new KubeClientException($"Failed to list API groups (HTTP status {responseMessage.StatusCode}).",
innerException: new HttpRequestException<StatusV1>(responseMessage.StatusCode,
response: await responseMessage.ReadContentAsAsync<StatusV1, StatusV1>().ConfigureAwait(false)
)
);
}
}
}
class ApiResourceClientV1
: KubeResourceClient
{
public ApiResourceClientV1(IKubeApiClient client)
: base(client)
{
}
public async Task<APIResourceListV1> List(string prefix, string groupVersion, CancellationToken cancellationToken = default)
{
if (String.IsNullOrWhiteSpace(groupVersion))
throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'groupVersion'.", nameof(groupVersion));
HttpRequest request = KubeRequest.Create("{Prefix}/{GroupVersion}")
.WithTemplateParameters(new
{
Prefix = prefix,
GroupVersion = groupVersion
});
using (HttpResponseMessage responseMessage = await Http.GetAsync(request, cancellationToken).ConfigureAwait(false))
{
if (responseMessage.IsSuccessStatusCode)
return await responseMessage.ReadContentAsAsync<APIResourceListV1>().ConfigureAwait(false);
if (responseMessage.Content.Headers.ContentType.MediaType == "application/json")
{
throw new KubeClientException($"Failed to list {groupVersion} APIs (HTTP status {responseMessage.StatusCode}).",
innerException: new HttpRequestException<StatusV1>(responseMessage.StatusCode,
response: await responseMessage.ReadContentAsAsync<StatusV1, StatusV1>().ConfigureAwait(false)
)
);
}
string responseBody = await responseMessage.Content.ReadAsStringAsync();
throw new KubeClientException(
$"Failed to list {groupVersion} APIs (HTTP status {responseMessage.StatusCode}).\nResponse from server:\n{responseBody}"
);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment