Kubernetes.Classic: add support for netstandard2.0 and net48 (#808)

* support gh nuget (#11)

* trim to fit net48

* add net48 support

* add very test framework

* add test body

* Revert "support gh nuget (#11)"

This reverts commit 5cdaf59690170be44e4554485fb2e89785a6a1cf.
This commit is contained in:
Boshi Lian
2022-04-03 16:35:42 -07:00
committed by GitHub
parent 92ccac423a
commit 8575548cd9
10 changed files with 225 additions and 5 deletions

View File

@@ -51,7 +51,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "yaml", "examples\yaml\yaml.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KubernetesClient.Models", "src\KubernetesClient.Models\KubernetesClient.Models.csproj", "{F066A4D8-2EF0-4C07-AC0D-BD325DE3FFA8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KubernetesClient.Basic", "src\KubernetesClient.Basic\KubernetesClient.Basic.csproj", "{927995F5-05CC-4078-8805-8E6CC06914D8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KubernetesClient.Basic", "src\KubernetesClient.Basic\KubernetesClient.Basic.csproj", "{927995F5-05CC-4078-8805-8E6CC06914D8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KubernetesClient.Classic", "src\KubernetesClient.Classic\KubernetesClient.Classic.csproj", "{80F19E8A-F097-4AA4-A68C-D417B96BBC68}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KubernetesClient.Classic.Tests", "tests\KubernetesClient.Classic.Tests\KubernetesClient.Classic.Tests.csproj", "{FD90C861-56C6-4536-B7F5-AC7779296384}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csrApproval", "examples\csrApproval\csrApproval.csproj", "{F626860C-F141-45B3-9DDD-88AD3932ACAF}"
EndProject
@@ -329,6 +333,30 @@ Global
{927995F5-05CC-4078-8805-8E6CC06914D8}.Release|x64.Build.0 = Release|Any CPU
{927995F5-05CC-4078-8805-8E6CC06914D8}.Release|x86.ActiveCfg = Release|Any CPU
{927995F5-05CC-4078-8805-8E6CC06914D8}.Release|x86.Build.0 = Release|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Debug|x64.ActiveCfg = Debug|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Debug|x64.Build.0 = Debug|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Debug|x86.ActiveCfg = Debug|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Debug|x86.Build.0 = Debug|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Release|Any CPU.Build.0 = Release|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Release|x64.ActiveCfg = Release|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Release|x64.Build.0 = Release|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Release|x86.ActiveCfg = Release|Any CPU
{80F19E8A-F097-4AA4-A68C-D417B96BBC68}.Release|x86.Build.0 = Release|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Debug|x64.ActiveCfg = Debug|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Debug|x64.Build.0 = Debug|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Debug|x86.ActiveCfg = Debug|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Debug|x86.Build.0 = Debug|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Release|Any CPU.Build.0 = Release|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Release|x64.ActiveCfg = Release|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Release|x64.Build.0 = Release|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Release|x86.ActiveCfg = Release|Any CPU
{FD90C861-56C6-4536-B7F5-AC7779296384}.Release|x86.Build.0 = Release|Any CPU
{F626860C-F141-45B3-9DDD-88AD3932ACAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F626860C-F141-45B3-9DDD-88AD3932ACAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F626860C-F141-45B3-9DDD-88AD3932ACAF}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -368,6 +396,8 @@ Global
{17AB0AD8-6C90-42DD-880C-16B5AC4A373F} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40}
{F066A4D8-2EF0-4C07-AC0D-BD325DE3FFA8} = {3D1864AA-1FFC-4512-BB13-46055E410F73}
{927995F5-05CC-4078-8805-8E6CC06914D8} = {3D1864AA-1FFC-4512-BB13-46055E410F73}
{80F19E8A-F097-4AA4-A68C-D417B96BBC68} = {3D1864AA-1FFC-4512-BB13-46055E410F73}
{FD90C861-56C6-4536-B7F5-AC7779296384} = {8AF4A5C2-F0CE-47D5-A4C5-FE4AB83CA509}
{F626860C-F141-45B3-9DDD-88AD3932ACAF} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

View File

@@ -0,0 +1,4 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Text.Json;

View File

@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net48</TargetFrameworks>
<RootNamespace>k8s</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
<PackageReference Include="System.IO.Abstractions" Version="13.2.47" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.13.1" />
<PackageReference Include="IdentityModel.OidcClient" Version="4.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\KubernetesClient.Models\KubernetesClient.Models.csproj" />
<ProjectReference Include="..\KubernetesClient.Basic\KubernetesClient.Basic.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\KubernetesClient\CertUtils.cs" />
<Compile Include="..\KubernetesClient\FileUtils.cs" />
<Compile Include="..\KubernetesClient\IKubernetes.cs" />
<Compile Include="..\KubernetesClient\Kubernetes.ConfigInit.cs" />
<Compile Include="..\KubernetesClient\Kubernetes.cs" />
<Compile Include="..\KubernetesClient\KubernetesClientConfiguration.ConfigFile.cs" />
<Compile Include="..\KubernetesClient\KubernetesClientConfiguration.InCluster.cs" />
<Compile Include="..\KubernetesClient\KubernetesClientConfiguration.cs" />
<Compile Include="..\KubernetesClient\KubernetesException.cs" />
<Compile Include="..\KubernetesClient\Exceptions\KubeConfigException.cs" />
<Compile Include="..\KubernetesClient\Exceptions\KubernetesClientException.cs" />
<Compile Include="..\KubernetesClient\Authentication\ExecTokenProvider.cs" />
<Compile Include="..\KubernetesClient\Authentication\GcpTokenProvider.cs" />
<Compile Include="..\KubernetesClient\Authentication\OidcTokenProvider.cs" />
<Compile Include="..\KubernetesClient\Authentication\TokenFileAuth.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Net.Http" Condition="'$(TargetFramework)' == 'net48'" />
</ItemGroup>
</Project>

View File

@@ -17,13 +17,21 @@ namespace k8s.Authentication
TokenFile = tokenFile;
}
#if NETSTANDARD2_1_OR_GREATER
public async Task<AuthenticationHeaderValue> GetAuthenticationHeaderAsync(CancellationToken cancellationToken)
#else
public Task<AuthenticationHeaderValue> GetAuthenticationHeaderAsync(CancellationToken cancellationToken)
#endif
{
if (TokenExpiresAt < DateTime.UtcNow)
{
#if NETSTANDARD2_1_OR_GREATER
token = await File.ReadAllTextAsync(TokenFile, cancellationToken)
.ContinueWith(r => r.Result.Trim(), cancellationToken)
.ConfigureAwait(false);
#else
token = File.ReadAllText(TokenFile).Trim();
#endif
// in fact, the token has a expiry of 10 minutes and kubelet
// refreshes it at 8 minutes of its lifetime. setting the expiry
// of 1 minute makes sure the token is reloaded regularly so
@@ -32,8 +40,11 @@ namespace k8s.Authentication
// < 10-8-1 minute.
TokenExpiresAt = DateTime.UtcNow.AddMinutes(1);
}
#if NETSTANDARD2_1_OR_GREATER
return new AuthenticationHeaderValue("Bearer", token);
#else
return Task.FromResult(new AuthenticationHeaderValue("Bearer", token));
#endif
}
}
}

View File

@@ -23,7 +23,9 @@ namespace k8s
Initialize();
ValidateConfig(config);
CaCerts = config.SslCaCerts;
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
SkipTlsVerify = config.SkipTlsVerify;
#endif
CreateHttpClient(handlers, config);
InitializeFromConfig(config);
HttpClientTimeout = config.HttpClientTimeout;
@@ -100,9 +102,11 @@ namespace k8s
private X509Certificate2Collection CaCerts { get; }
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
private X509Certificate2 ClientCert { get; }
private bool SkipTlsVerify { get; }
#endif
// NOTE: this method replicates the logic that the base ServiceClient uses except that it doesn't insert the RetryDelegatingHandler
// and it does insert the WatcherDelegatingHandler. we don't want the RetryDelegatingHandler because it has a very broad definition

View File

@@ -71,7 +71,11 @@ namespace k8s
if (watch == true)
{
#if NETSTANDARD2_0 || NET48
throw new KubernetesException("watch not supported");
#else
httpResponse.Content = new LineSeparatedHttpContent(httpResponse.Content, cancellationToken);
#endif
}
try
@@ -100,7 +104,9 @@ namespace k8s
var httpRequest = new HttpRequestMessage();
httpRequest.Method = new HttpMethod(method);
httpRequest.RequestUri = new Uri(BaseUri, relativeUri);
#if NETSTANDARD2_1_OR_GREATER
httpRequest.Version = HttpVersion.Version20;
#endif
// Set Headers
if (customHeaders != null)
{

View File

@@ -3,5 +3,6 @@
<ProjectReference Include="KubernetesClient.Models/KubernetesClient.Models.csproj" />
<ProjectReference Include="KubernetesClient.Basic/KubernetesClient.Basic.csproj" />
<ProjectReference Include="KubernetesClient/KubernetesClient.csproj" />
<ProjectReference Include="KubernetesClient.Classic/KubernetesClient.Classic.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<RootNamespace>k8s.Tests</RootNamespace>
<TargetFramework Condition="'$(OS)' != 'Windows_NT'">net6</TargetFramework>
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">net6;net48</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MartinCostello.Logging.XUnit" Version="0.2.0" />
<PackageReference Include="FluentAssertions" Version="6.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="13.2.47" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
<PackageReference Include="AutoMapper" Version="10.1.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Xunit.StaFact" Version="1.0.37" />
<PackageReference Include="Moq" Version="4.16.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\KubernetesClient.Classic\KubernetesClient.Classic.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,81 @@
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Xunit;
using k8s.Models;
namespace k8s.tests;
public class BasicTests
{
// TODO: fail to setup asp.net core 6 on net48
private class DummyHttpServer : System.IDisposable
{
private TcpListener server;
private readonly Task loop;
private volatile bool running = false;
public string Addr => $"http://{server.LocalEndpoint}";
public DummyHttpServer(object obj)
{
server = new TcpListener(IPAddress.Parse("127.0.0.1"), 0);
server.Start();
running = true;
loop = Task.Run(async () =>
{
while (running)
{
var result = KubernetesJson.Serialize(obj);
var client = await server.AcceptTcpClientAsync().ConfigureAwait(false);
var stream = client.GetStream();
stream.Read(new byte[1024], 0, 1024); // TODO ensure full header
var writer = new StreamWriter(stream);
await writer.WriteLineAsync("HTTP/1.0 200 OK").ConfigureAwait(false);
await writer.WriteLineAsync("Content-Length: " + result.Length).ConfigureAwait(false);
await writer.WriteLineAsync("Content-Type: application/json").ConfigureAwait(false);
await writer.WriteLineAsync().ConfigureAwait(false);
await writer.WriteLineAsync(result).ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);
client.Close();
}
});
}
public void Dispose()
{
try
{
running = false;
server.Stop();
loop.Wait();
loop.Dispose();
}
catch
{
// ignore
}
}
}
[Fact]
public async Task QueryPods()
{
using var server = new DummyHttpServer(new V1Pod()
{
Metadata = new V1ObjectMeta()
{
Name = "pod0",
},
});
var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Addr });
var pod = await client.ReadNamespacedPodAsync("pod", "default").ConfigureAwait(false);
Assert.Equal("pod0", pod.Metadata.Name);
}
}

View File

@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<IsPackable>false</IsPackable>
<LangVersion>8</LangVersion>
<SignAssembly>true</SignAssembly>
<RootNamespace>k8s.Tests</RootNamespace>
<TargetFrameworks>netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
</PropertyGroup>