diff --git a/kubernetes-client.sln b/kubernetes-client.sln index 58577c9..98edcf2 100644 --- a/kubernetes-client.sln +++ b/kubernetes-client.sln @@ -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 diff --git a/src/KubernetesClient.Classic/Global.cs b/src/KubernetesClient.Classic/Global.cs new file mode 100644 index 0000000..f96593e --- /dev/null +++ b/src/KubernetesClient.Classic/Global.cs @@ -0,0 +1,4 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Text.Json; diff --git a/src/KubernetesClient.Classic/KubernetesClient.Classic.csproj b/src/KubernetesClient.Classic/KubernetesClient.Classic.csproj new file mode 100644 index 0000000..b4041cd --- /dev/null +++ b/src/KubernetesClient.Classic/KubernetesClient.Classic.csproj @@ -0,0 +1,43 @@ + + + + netstandard2.0;net48 + k8s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/KubernetesClient/Authentication/TokenFileAuth.cs b/src/KubernetesClient/Authentication/TokenFileAuth.cs index 181f09b..1925b3c 100644 --- a/src/KubernetesClient/Authentication/TokenFileAuth.cs +++ b/src/KubernetesClient/Authentication/TokenFileAuth.cs @@ -17,13 +17,21 @@ namespace k8s.Authentication TokenFile = tokenFile; } +#if NETSTANDARD2_1_OR_GREATER public async Task GetAuthenticationHeaderAsync(CancellationToken cancellationToken) +#else + public Task 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 } } } diff --git a/src/KubernetesClient/Kubernetes.ConfigInit.cs b/src/KubernetesClient/Kubernetes.ConfigInit.cs index e6a8bbf..fd11e40 100644 --- a/src/KubernetesClient/Kubernetes.ConfigInit.cs +++ b/src/KubernetesClient/Kubernetes.ConfigInit.cs @@ -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 diff --git a/src/KubernetesClient/Kubernetes.cs b/src/KubernetesClient/Kubernetes.cs index 506d7a4..e4a3f5b 100644 --- a/src/KubernetesClient/Kubernetes.cs +++ b/src/KubernetesClient/Kubernetes.cs @@ -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) { diff --git a/src/nuget.proj b/src/nuget.proj index 236467f..fa0a58d 100644 --- a/src/nuget.proj +++ b/src/nuget.proj @@ -3,5 +3,6 @@ + diff --git a/tests/KubernetesClient.Classic.Tests/KubernetesClient.Classic.Tests.csproj b/tests/KubernetesClient.Classic.Tests/KubernetesClient.Classic.Tests.csproj new file mode 100644 index 0000000..1e9f3a4 --- /dev/null +++ b/tests/KubernetesClient.Classic.Tests/KubernetesClient.Classic.Tests.csproj @@ -0,0 +1,42 @@ + + + false + k8s.Tests + net6 + net6;net48 + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/tests/KubernetesClient.Classic.Tests/SimpleTests.cs b/tests/KubernetesClient.Classic.Tests/SimpleTests.cs new file mode 100644 index 0000000..0520299 --- /dev/null +++ b/tests/KubernetesClient.Classic.Tests/SimpleTests.cs @@ -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); + } +} diff --git a/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj b/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj index bbd49e2..943de42 100755 --- a/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj +++ b/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj @@ -1,8 +1,6 @@ - + false - 8 - true k8s.Tests netcoreapp3.1;net5.0;net6.0