diff --git a/examples/attach/Attach.cs b/examples/attach/Attach.cs index 2542e1c..d15d7fe 100755 --- a/examples/attach/Attach.cs +++ b/examples/attach/Attach.cs @@ -2,7 +2,7 @@ using System; using System.Threading.Tasks; using k8s; using k8s.Models; -using Microsoft.Rest; +using k8s.Autorest; namespace attach { @@ -10,8 +10,6 @@ namespace attach { private static async Task Main(string[] args) { - ServiceClientTracing.IsEnabled = true; - var config = KubernetesClientConfiguration.BuildConfigFromConfigFile(); IKubernetes client = new Kubernetes(config); Console.WriteLine("Starting Request!"); diff --git a/examples/customResource/Program.cs b/examples/customResource/Program.cs index 0be2560..2ea51ed 100644 --- a/examples/customResource/Program.cs +++ b/examples/customResource/Program.cs @@ -1,4 +1,5 @@ using k8s; +using k8s.Autorest; using k8s.Models; using Microsoft.AspNetCore.JsonPatch; using System; @@ -35,14 +36,14 @@ namespace customResource myCr.Metadata.NamespaceProperty ?? "default", myCRD.PluralName).ConfigureAwait(false); } - catch (Microsoft.Rest.HttpOperationException httpOperationException) when (httpOperationException.Message.Contains("422")) + catch (HttpOperationException httpOperationException) when (httpOperationException.Message.Contains("422")) { var phase = httpOperationException.Response.ReasonPhrase; var content = httpOperationException.Response.Content; Console.WriteLine("response content: {0}", content); Console.WriteLine("response phase: {0}", phase); } - catch (Microsoft.Rest.HttpOperationException) + catch (HttpOperationException) { } @@ -70,7 +71,7 @@ namespace customResource myCRD.PluralName, myCr.Metadata.Name).ConfigureAwait(false); } - catch (Microsoft.Rest.HttpOperationException httpOperationException) + catch (HttpOperationException httpOperationException) { var phase = httpOperationException.Response.ReasonPhrase; var content = httpOperationException.Response.Content; diff --git a/examples/httpClientFactory/PodListHostedService.cs b/examples/httpClientFactory/PodListHostedService.cs deleted file mode 100644 index c104fc8..0000000 --- a/examples/httpClientFactory/PodListHostedService.cs +++ /dev/null @@ -1,44 +0,0 @@ -using k8s; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace httpClientFactory -{ - // Learn more about IHostedServices at https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual-studio - public class PodListHostedService : IHostedService - { - private readonly IKubernetes _kubernetesClient; - private readonly ILogger _logger; - - public PodListHostedService(IKubernetes kubernetesClient, ILogger logger) - { - _kubernetesClient = kubernetesClient ?? throw new ArgumentNullException(nameof(kubernetesClient)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - _logger.LogInformation("Starting Request!"); - - var list = await _kubernetesClient.ListNamespacedPodAsync("default", cancellationToken: cancellationToken).ConfigureAwait(false); - foreach (var item in list.Items) - { - _logger.LogInformation(item.Metadata.Name); - } - - if (list.Items.Count == 0) - { - _logger.LogInformation("Empty!"); - } - } - - public Task StopAsync(CancellationToken cancellationToken) - { - // Nothing to stop - return Task.CompletedTask; - } - } -} diff --git a/examples/httpClientFactory/Program.cs b/examples/httpClientFactory/Program.cs deleted file mode 100644 index e3c332d..0000000 --- a/examples/httpClientFactory/Program.cs +++ /dev/null @@ -1,44 +0,0 @@ -using k8s; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.DependencyInjection; -using System.Threading.Tasks; - -namespace httpClientFactory -{ - internal class Program - { - public static async Task Main(string[] args) - { - // Learn more about generic hosts at https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host - using (var host = new HostBuilder() - .ConfigureLogging((logging) => { logging.AddConsole(); }) - .ConfigureServices((hostBuilderContext, services) => - { - // Ideally this config would be read from the .net core config constructs, - // but that has not been implemented in the KubernetesClient library at - // the time this sample was created. - var config = KubernetesClientConfiguration.BuildDefaultConfig(); - services.AddSingleton(config); - - // Setup the http client - services.AddHttpClient("K8s") - .AddTypedClient((httpClient, serviceProvider) => - { - return new Kubernetes( - serviceProvider.GetRequiredService(), - httpClient); - }) - .ConfigurePrimaryHttpMessageHandler(config.CreateDefaultHttpClientHandler); - - // Add the class that uses the client - services.AddHostedService(); - }) - .Build()) - { - await host.StartAsync().ConfigureAwait(false); - await host.StopAsync().ConfigureAwait(false); - } - } - } -} diff --git a/examples/httpClientFactory/httpClientFactory.csproj b/examples/httpClientFactory/httpClientFactory.csproj deleted file mode 100644 index 0394190..0000000 --- a/examples/httpClientFactory/httpClientFactory.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - Exe - net5 - - - - - - - - - - - - - diff --git a/examples/namespace/NamespaceExample.cs b/examples/namespace/NamespaceExample.cs index 83fd3c8..6db01b8 100644 --- a/examples/namespace/NamespaceExample.cs +++ b/examples/namespace/NamespaceExample.cs @@ -35,9 +35,9 @@ namespace @namespace { foreach (var innerEx in ex.InnerExceptions) { - if (innerEx is Microsoft.Rest.HttpOperationException) + if (innerEx is k8s.Autorest.HttpOperationException) { - var code = ((Microsoft.Rest.HttpOperationException)innerEx).Response.StatusCode; + var code = ((k8s.Autorest.HttpOperationException)innerEx).Response.StatusCode; if (code == HttpStatusCode.NotFound) { return; @@ -47,7 +47,7 @@ namespace @namespace } } } - catch (Microsoft.Rest.HttpOperationException ex) + catch (k8s.Autorest.HttpOperationException ex) { if (ex.Response.StatusCode == HttpStatusCode.NotFound) { diff --git a/examples/watch/Program.cs b/examples/watch/Program.cs index 8b80922..fff0bbb 100644 --- a/examples/watch/Program.cs +++ b/examples/watch/Program.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; using k8s; using k8s.Models; -using Microsoft.Rest; +using k8s.Autorest; namespace watch { diff --git a/gen/LibKubernetesGenerator/LibKubernetesGenerator.csproj b/gen/LibKubernetesGenerator/LibKubernetesGenerator.csproj index 5e89fb4..c8fe907 100644 --- a/gen/LibKubernetesGenerator/LibKubernetesGenerator.csproj +++ b/gen/LibKubernetesGenerator/LibKubernetesGenerator.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 10.0 @@ -19,6 +19,7 @@ + @@ -35,6 +36,7 @@ + diff --git a/gen/LibKubernetesGenerator/templates/IKubernetes.cs.template b/gen/LibKubernetesGenerator/templates/IKubernetes.cs.template index d23f05d..40e5fab 100644 --- a/gen/LibKubernetesGenerator/templates/IKubernetes.cs.template +++ b/gen/LibKubernetesGenerator/templates/IKubernetes.cs.template @@ -6,7 +6,7 @@ namespace k8s { - using Microsoft.Rest; + using k8s.Autorest; using Models; using System.Collections; @@ -17,19 +17,8 @@ namespace k8s /// /// - public partial interface IKubernetes : System.IDisposable + public partial interface IKubernetes { - /// - /// The base URI of the service. - /// - System.Uri BaseUri { get; set; } - - /// - /// Subscription credentials which uniquely identify client - /// subscription. - /// - ServiceClientCredentials Credentials { get; } - {{#.}} /// /// {{ToXmlDoc operation.description}} diff --git a/gen/LibKubernetesGenerator/templates/Kubernetes.cs.template b/gen/LibKubernetesGenerator/templates/Kubernetes.cs.template index e9ce1b9..3f6bbcc 100644 --- a/gen/LibKubernetesGenerator/templates/Kubernetes.cs.template +++ b/gen/LibKubernetesGenerator/templates/Kubernetes.cs.template @@ -6,7 +6,7 @@ namespace k8s { - using Microsoft.Rest; + using k8s.Autorest; using Models; using System.Collections.Generic; using System.IO; @@ -15,7 +15,7 @@ namespace k8s using System.Threading; using System.Threading.Tasks; - public partial class Kubernetes : ServiceClient, IKubernetes + public partial class Kubernetes : IKubernetes { {{#.}} /// diff --git a/kubernetes-client.sln b/kubernetes-client.sln index b533b8b..ca62d27 100644 --- a/kubernetes-client.sln +++ b/kubernetes-client.sln @@ -31,8 +31,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{879F8787-C3B EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "patch", "examples\patch\patch.csproj", "{04DE2C84-117D-4E21-8B45-B7AE627697BD}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "httpClientFactory", "examples\httpClientFactory\httpClientFactory.csproj", "{A07314A0-02E8-4F36-B233-726D59D28F08}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "metrics", "examples\metrics\metrics.csproj", "{B9647AD4-F6B0-406F-8B79-6781E31600EC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2E.Tests", "tests\E2E.Tests\E2E.Tests.csproj", "{5056C4A2-5E12-4C16-8DA7-8835DA58BFF2}" @@ -177,18 +175,6 @@ Global {04DE2C84-117D-4E21-8B45-B7AE627697BD}.Release|x64.Build.0 = Release|Any CPU {04DE2C84-117D-4E21-8B45-B7AE627697BD}.Release|x86.ActiveCfg = Release|Any CPU {04DE2C84-117D-4E21-8B45-B7AE627697BD}.Release|x86.Build.0 = Release|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Debug|x64.ActiveCfg = Debug|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Debug|x64.Build.0 = Debug|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Debug|x86.ActiveCfg = Debug|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Debug|x86.Build.0 = Debug|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Release|Any CPU.Build.0 = Release|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Release|x64.ActiveCfg = Release|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Release|x64.Build.0 = Release|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Release|x86.ActiveCfg = Release|Any CPU - {A07314A0-02E8-4F36-B233-726D59D28F08}.Release|x86.Build.0 = Release|Any CPU {B9647AD4-F6B0-406F-8B79-6781E31600EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9647AD4-F6B0-406F-8B79-6781E31600EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {B9647AD4-F6B0-406F-8B79-6781E31600EC}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -288,7 +274,6 @@ Global {35DD7248-F9EC-4272-A32C-B0C59E5A6FA7} = {3D1864AA-1FFC-4512-BB13-46055E410F73} {806AD0E5-833F-42FB-A870-4BCEE7F4B17F} = {8AF4A5C2-F0CE-47D5-A4C5-FE4AB83CA509} {04DE2C84-117D-4E21-8B45-B7AE627697BD} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40} - {A07314A0-02E8-4F36-B233-726D59D28F08} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40} {B9647AD4-F6B0-406F-8B79-6781E31600EC} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40} {5056C4A2-5E12-4C16-8DA7-8835DA58BFF2} = {8AF4A5C2-F0CE-47D5-A4C5-FE4AB83CA509} {4D2AE427-F856-49E5-B61D-EA6B17D89051} = {8AF4A5C2-F0CE-47D5-A4C5-FE4AB83CA509} diff --git a/src/KubernetesClient/Authentication/ExecTokenProvider.cs b/src/KubernetesClient/Authentication/ExecTokenProvider.cs index cfe70af..fba1d59 100644 --- a/src/KubernetesClient/Authentication/ExecTokenProvider.cs +++ b/src/KubernetesClient/Authentication/ExecTokenProvider.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; using k8s.Exceptions; using k8s.KubeConfigModels; -using Microsoft.Rest; +using k8s.Autorest; namespace k8s.Authentication { diff --git a/src/KubernetesClient/Authentication/GcpTokenProvider.cs b/src/KubernetesClient/Authentication/GcpTokenProvider.cs index ea1f7bd..de6671d 100644 --- a/src/KubernetesClient/Authentication/GcpTokenProvider.cs +++ b/src/KubernetesClient/Authentication/GcpTokenProvider.cs @@ -3,7 +3,7 @@ using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using k8s.Exceptions; -using Microsoft.Rest; +using k8s.Autorest; namespace k8s.Authentication { diff --git a/src/KubernetesClient/Authentication/OidcTokenProvider.cs b/src/KubernetesClient/Authentication/OidcTokenProvider.cs index 01fe529..51a6a22 100644 --- a/src/KubernetesClient/Authentication/OidcTokenProvider.cs +++ b/src/KubernetesClient/Authentication/OidcTokenProvider.cs @@ -1,6 +1,6 @@ using IdentityModel.OidcClient; using k8s.Exceptions; -using Microsoft.Rest; +using k8s.Autorest; using System.IdentityModel.Tokens.Jwt; using System.Net.Http.Headers; using System.Threading; diff --git a/src/KubernetesClient/Authentication/TokenFileAuth.cs b/src/KubernetesClient/Authentication/TokenFileAuth.cs index fd8b324..181f09b 100644 --- a/src/KubernetesClient/Authentication/TokenFileAuth.cs +++ b/src/KubernetesClient/Authentication/TokenFileAuth.cs @@ -2,7 +2,7 @@ using System.Net.Http.Headers; using System.IO; using System.Threading; using System.Threading.Tasks; -using Microsoft.Rest; +using k8s.Autorest; namespace k8s.Authentication { diff --git a/src/KubernetesClient/Autorest/BasicAuthenticationCredentials.cs b/src/KubernetesClient/Autorest/BasicAuthenticationCredentials.cs new file mode 100644 index 0000000..d543611 --- /dev/null +++ b/src/KubernetesClient/Autorest/BasicAuthenticationCredentials.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace k8s.Autorest +{ + /// + /// Basic Auth credentials for use with a REST Service Client. + /// + public class BasicAuthenticationCredentials : ServiceClientCredentials + { + /// + /// Basic auth UserName. + /// + public string UserName { get; set; } + + /// + /// Basic auth password. + /// + public string Password { get; set; } + + /// + /// Add the Basic Authentication Header to each outgoing request + /// + /// The outgoing request + /// A token to cancel the operation + /// void + public override Task ProcessHttpRequestAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + if (request == null) + { + throw new ArgumentNullException("request"); + } + + // Add username and password to "Basic" header of each request. + request.Headers.Authorization = new AuthenticationHeaderValue( + "Basic", + Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format( + CultureInfo.InvariantCulture, + "{0}:{1}", + UserName, + Password).ToCharArray()))); + return Task.FromResult(null); + } + } +} diff --git a/src/KubernetesClient/Autorest/HttpExtensions.cs b/src/KubernetesClient/Autorest/HttpExtensions.cs new file mode 100644 index 0000000..7ac6b6d --- /dev/null +++ b/src/KubernetesClient/Autorest/HttpExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; + +namespace k8s.Autorest +{ + /// + /// Extensions for manipulating HTTP request and response objects. + /// + internal static class HttpExtensions + { + /// + /// Get the content headers of an HtttRequestMessage. + /// + /// The request message. + /// The content headers. + public static HttpHeaders GetContentHeaders(this HttpRequestMessage request) + { + if (request != null && request.Content != null) + { + return request.Content.Headers; + } + + return null; + } + + /// + /// Get the content headers of an HttpResponseMessage. + /// + /// The response message. + /// The content headers. + public static HttpHeaders GetContentHeaders(this HttpResponseMessage response) + { + if (response != null && response.Content != null) + { + return response.Content.Headers; + } + + return null; + } + } +} diff --git a/src/KubernetesClient/Autorest/HttpMessageWrapper.cs b/src/KubernetesClient/Autorest/HttpMessageWrapper.cs new file mode 100644 index 0000000..3695e02 --- /dev/null +++ b/src/KubernetesClient/Autorest/HttpMessageWrapper.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; + +namespace k8s.Autorest +{ + /// + /// Base class used to wrap HTTP requests and responses to preserve data after disposal of + /// HttpClient. + /// + public abstract class HttpMessageWrapper + { + /// + /// Initializes a new instance of the class. + /// + protected HttpMessageWrapper() + { + Headers = new Dictionary>(); + } + + /// + /// Exposes the HTTP message contents. + /// + public string Content { get; set; } + + /// + /// Gets the collection of HTTP headers. + /// + public IDictionary> Headers { get; private set; } + + /// + /// Copies HTTP message headers to the error object. + /// + /// Collection of HTTP headers. + protected void CopyHeaders(HttpHeaders headers) + { + if (headers != null) + { + foreach (KeyValuePair> header in headers) + { + IEnumerable values = null; + if (Headers.TryGetValue(header.Key, out values)) + { + values = Enumerable.Concat(values, header.Value); + } + else + { + values = header.Value; + } + + Headers[header.Key] = values; + } + } + } + } +} diff --git a/src/KubernetesClient/Autorest/HttpOperationException.cs b/src/KubernetesClient/Autorest/HttpOperationException.cs new file mode 100644 index 0000000..927d1e8 --- /dev/null +++ b/src/KubernetesClient/Autorest/HttpOperationException.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Runtime.Serialization; +using System.Security.Permissions; + +namespace k8s.Autorest +{ + /// + /// Exception thrown for an invalid response with custom error information. + /// + [Serializable] + public class HttpOperationException : RestException + { + /// + /// Gets information about the associated HTTP request. + /// + public HttpRequestMessageWrapper Request { get; set; } + + /// + /// Gets information about the associated HTTP response. + /// + public HttpResponseMessageWrapper Response { get; set; } + + /// + /// Gets or sets the response object. + /// + public object Body { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public HttpOperationException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The exception message. + public HttpOperationException(string message) + : this(message, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The exception message. + /// Inner exception. + public HttpOperationException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Serialization info. + /// Streaming context. + protected HttpOperationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/KubernetesClient/Autorest/HttpOperationResponse.cs b/src/KubernetesClient/Autorest/HttpOperationResponse.cs new file mode 100644 index 0000000..4cdabdc --- /dev/null +++ b/src/KubernetesClient/Autorest/HttpOperationResponse.cs @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; + +namespace k8s.Autorest +{ + /// + /// Represents the base return type of all ServiceClient REST operations without response body. + /// +#pragma warning disable SA1649 // File name should match first type name + public interface IHttpOperationResponse +#pragma warning restore SA1649 // File name should match first type name + { + /// + /// Gets information about the associated HTTP request. + /// + HttpRequestMessage Request { get; set; } + + /// + /// Gets information about the associated HTTP response. + /// + HttpResponseMessage Response { get; set; } + } + + /// + /// Represents the base return type of all ServiceClient REST operations with response body. + /// +#pragma warning disable SA1618 // Generic type parameters should be documented + public interface IHttpOperationResponse : IHttpOperationResponse +#pragma warning restore SA1618 // Generic type parameters should be documented + { + /// + /// Gets or sets the response object. + /// + T Body { get; set; } + } + +#pragma warning disable SA1622 // Generic type parameter documentation should have text + /// + /// Represents the base return type of all ServiceClient REST operations with a header response. + /// + /// + public interface IHttpOperationHeaderResponse : IHttpOperationResponse +#pragma warning restore SA1622 // Generic type parameter documentation should have text + { + /// + /// Gets or sets the response header object. + /// + T Headers { get; set; } + } + + /// + /// Represents the base return type of all ServiceClient REST operations with response body and header. + /// +#pragma warning disable SA1618 // Generic type parameters should be documented + public interface IHttpOperationResponse : IHttpOperationResponse, IHttpOperationHeaderResponse +#pragma warning restore SA1618 // Generic type parameters should be documented + { + } + + /// + /// Represents the base return type of all ServiceClient REST operations without response body. + /// + public class HttpOperationResponse : IHttpOperationResponse, IDisposable + { + /// + /// Indicates whether the HttpOperationResponse has been disposed. + /// + private bool _disposed; + + /// + /// Gets information about the associated HTTP request. + /// + public HttpRequestMessage Request { get; set; } + + /// + /// Gets information about the associated HTTP response. + /// + public HttpResponseMessage Response { get; set; } + + /// + /// Dispose the HttpOperationResponse. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose the HttpClient and Handlers. + /// + /// True to release both managed and unmanaged resources; false to releases only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + _disposed = true; + + // Dispose the request and response + if (Request != null) + { + Request.Dispose(); + } + + if (Response != null) + { + Response.Dispose(); + } + + Request = null; + Response = null; + } + } + } + + /// + /// Represents the base return type of all ServiceClient REST operations. + /// +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1618 // Generic type parameters should be documented + public class HttpOperationResponse : HttpOperationResponse, IHttpOperationResponse +#pragma warning restore SA1618 // Generic type parameters should be documented +#pragma warning restore SA1402 // File may only contain a single type + { + /// + /// Gets or sets the response object. + /// + public T Body { get; set; } + } +} diff --git a/src/KubernetesClient/Autorest/HttpRequestMessageWrapper.cs b/src/KubernetesClient/Autorest/HttpRequestMessageWrapper.cs new file mode 100644 index 0000000..c1a6ef9 --- /dev/null +++ b/src/KubernetesClient/Autorest/HttpRequestMessageWrapper.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace k8s.Autorest +{ + /// + /// Wrapper around HttpRequestMessage type that copies properties of HttpRequestMessage so that + /// they are available after the HttpClient gets disposed. + /// + public class HttpRequestMessageWrapper : HttpMessageWrapper + { + /// + /// Initializes a new instance of the class from HttpRequestMessage. + /// and content. + /// +#pragma warning disable SA1611 // Element parameters should be documented + public HttpRequestMessageWrapper(HttpRequestMessage httpRequest, string content) +#pragma warning restore SA1611 // Element parameters should be documented + { + if (httpRequest == null) + { + throw new ArgumentNullException("httpRequest"); + } + + this.CopyHeaders(httpRequest.Headers); + this.CopyHeaders(httpRequest.GetContentHeaders()); + + this.Content = content; + this.Method = httpRequest.Method; + this.RequestUri = httpRequest.RequestUri; +#pragma warning disable CS0618 // Type or member is obsolete + if (httpRequest.Properties != null) + { + Properties = new Dictionary(); + foreach (KeyValuePair pair in httpRequest.Properties) +#pragma warning restore CS0618 // Type or member is obsolete + { + this.Properties[pair.Key] = pair.Value; + } + } + } + + /// + /// Gets or sets the HTTP method used by the HTTP request message. + /// + public HttpMethod Method { get; protected set; } + + /// + /// Gets or sets the Uri used for the HTTP request. + /// + public Uri RequestUri { get; protected set; } + + /// + /// Gets a set of properties for the HTTP request. + /// + public IDictionary Properties { get; private set; } + } +} diff --git a/src/KubernetesClient/Autorest/HttpResponseMessageWrapper.cs b/src/KubernetesClient/Autorest/HttpResponseMessageWrapper.cs new file mode 100644 index 0000000..471c855 --- /dev/null +++ b/src/KubernetesClient/Autorest/HttpResponseMessageWrapper.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Http; + +namespace k8s.Autorest +{ + /// + /// Wrapper around HttpResponseMessage type that copies properties of HttpResponseMessage so that + /// they are available after the HttpClient gets disposed. + /// + public class HttpResponseMessageWrapper : HttpMessageWrapper + { + /// + /// Initializes a new instance of the class from HttpResponseMessage. + /// and content. + /// +#pragma warning disable SA1611 // Element parameters should be documented + public HttpResponseMessageWrapper(HttpResponseMessage httpResponse, string content) +#pragma warning restore SA1611 // Element parameters should be documented + { + if (httpResponse == null) + { + throw new ArgumentNullException("httpResponse"); + } + + this.CopyHeaders(httpResponse.Headers); + this.CopyHeaders(httpResponse.GetContentHeaders()); + + this.Content = content; + this.StatusCode = httpResponse.StatusCode; + this.ReasonPhrase = httpResponse.ReasonPhrase; + } + + /// + /// Gets or sets the status code of the HTTP response. + /// + public HttpStatusCode StatusCode { get; protected set; } + + /// + /// Exposes the reason phrase, typically sent along with the status code. + /// + public string ReasonPhrase { get; protected set; } + } +} diff --git a/src/KubernetesClient/Autorest/ITokenProvider.cs b/src/KubernetesClient/Autorest/ITokenProvider.cs new file mode 100644 index 0000000..3d9bc99 --- /dev/null +++ b/src/KubernetesClient/Autorest/ITokenProvider.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +#pragma warning disable SA1606 +#pragma warning disable SA1614 +namespace k8s.Autorest +{ + /// + /// Interface to a source of access tokens. + /// + public interface ITokenProvider + { + /// + /// + /// + /// + /// AuthenticationHeaderValue + Task GetAuthenticationHeaderAsync(CancellationToken cancellationToken); + } +} +#pragma warning restore SA1614 +#pragma warning restore SA1606 diff --git a/src/KubernetesClient/Autorest/RestException.cs b/src/KubernetesClient/Autorest/RestException.cs new file mode 100644 index 0000000..1c418cc --- /dev/null +++ b/src/KubernetesClient/Autorest/RestException.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Runtime.Serialization; + +namespace k8s.Autorest +{ + /// + /// Generic exception for Microsoft Rest Client. + /// + [Serializable] + public class RestException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public RestException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The exception message. + public RestException(string message) + : this(message, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The exception message. + /// Inner exception. + public RestException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Serialization info. + /// Streaming context. + protected RestException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/KubernetesClient/Autorest/ServiceClientCredentials.cs b/src/KubernetesClient/Autorest/ServiceClientCredentials.cs new file mode 100644 index 0000000..564cb0c --- /dev/null +++ b/src/KubernetesClient/Autorest/ServiceClientCredentials.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace k8s.Autorest +{ + /// + /// ServiceClientCredentials is the abstraction for credentials used by ServiceClients accessing REST services. + /// + public abstract class ServiceClientCredentials + { + /// + /// Apply the credentials to the HTTP request. + /// + /// The HTTP request message. + /// Cancellation token. + /// + /// Task that will complete when processing has finished. + /// + public virtual Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + // Return an empty task by default + return Task.FromResult(null); + } + } +} diff --git a/src/KubernetesClient/Autorest/StringTokenProvider.cs b/src/KubernetesClient/Autorest/StringTokenProvider.cs new file mode 100644 index 0000000..e070cea --- /dev/null +++ b/src/KubernetesClient/Autorest/StringTokenProvider.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace k8s.Autorest +{ + /// + /// A simple token provider that always provides a static access token. + /// + public sealed class StringTokenProvider : ITokenProvider + { + private string _accessToken; + private string _type; + + /// + /// Initializes a new instance of the class. + /// Create a token provider for the given token type that returns the given + /// access token. + /// + /// The access token to return. + /// The token type of the given access token. + public StringTokenProvider(string accessToken, string tokenType) + { + _accessToken = accessToken; + _type = tokenType; + } + + /// + /// Gets the token type of this access token. + /// + public string TokenType + { + get { return _type; } + } + + /// + /// Returns the static access token. + /// + /// The cancellation token for this action. + /// This will not be used since the returned token is static. + /// The access token. + public Task GetAuthenticationHeaderAsync(CancellationToken cancellationToken) + { + return Task.FromResult(new AuthenticationHeaderValue(_type, _accessToken)); + } + } +} diff --git a/src/KubernetesClient/Autorest/TokenCredentials.cs b/src/KubernetesClient/Autorest/TokenCredentials.cs new file mode 100644 index 0000000..c51b04e --- /dev/null +++ b/src/KubernetesClient/Autorest/TokenCredentials.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace k8s.Autorest +{ + /// + /// Token based credentials for use with a REST Service Client. + /// + public class TokenCredentials : ServiceClientCredentials + { + /// + /// The bearer token type, as serialized in an http Authentication header. + /// + private const string BearerTokenType = "Bearer"; + + /// + /// Gets secure token used to authenticate against Microsoft Azure API. + /// No anonymous requests are allowed. + /// + protected ITokenProvider TokenProvider { get; private set; } + + /// + /// Gets Tenant ID + /// + public string TenantId { get; private set; } + + /// + /// Gets UserInfo.DisplayableId + /// + public string CallerId { get; private set; } + + /// + /// Initializes a new instance of the + /// class with the given 'Bearer' token. + /// + /// Valid JSON Web Token (JWT). + public TokenCredentials(string token) + : this(token, BearerTokenType) + { + } + + /// + /// Initializes a new instance of the + /// class with the given token and token type. + /// + /// Valid JSON Web Token (JWT). + /// The token type of the given token. + public TokenCredentials(string token, string tokenType) + : this(new StringTokenProvider(token, tokenType)) + { + if (string.IsNullOrEmpty(token)) + { + throw new ArgumentNullException("token"); + } + + if (string.IsNullOrEmpty(tokenType)) + { + throw new ArgumentNullException("tokenType"); + } + } + + /// + /// Initializes a new instance of the class. + /// Create an access token credentials object, given an interface to a token source. + /// + /// The source of tokens for these credentials. + public TokenCredentials(ITokenProvider tokenProvider) + { + if (tokenProvider == null) + { + throw new ArgumentNullException("tokenProvider"); + } + + this.TokenProvider = tokenProvider; + } + + /// + /// Initializes a new instance of the class. + /// Create an access token credentials object, given an interface to a token source. + /// + /// The source of tokens for these credentials. + /// Tenant ID from AuthenticationResult + /// UserInfo.DisplayableId field from AuthenticationResult + public TokenCredentials(ITokenProvider tokenProvider, string tenantId, string callerId) + : this(tokenProvider) + { + this.TenantId = tenantId; + this.CallerId = callerId; + } + + /// + /// Apply the credentials to the HTTP request. + /// + /// The HTTP request. + /// Cancellation token. + /// + /// Task that will complete when processing has completed. + /// + public async override Task ProcessHttpRequestAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (TokenProvider == null) + { + throw new ArgumentNullException(nameof(TokenProvider)); + } + + request.Headers.Authorization = await TokenProvider.GetAuthenticationHeaderAsync(cancellationToken).ConfigureAwait(false); + await base.ProcessHttpRequestAsync(request, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/KubernetesClient/IKubernetes.HttpClient.cs b/src/KubernetesClient/IKubernetes.HttpClient.cs deleted file mode 100644 index e5bc74b..0000000 --- a/src/KubernetesClient/IKubernetes.HttpClient.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Net.Http; - -namespace k8s -{ - public partial interface IKubernetes - { - /// - /// Gets the used for making HTTP requests. - /// - HttpClient HttpClient { get; } - } -} diff --git a/src/KubernetesClient/IKubernetes.cs b/src/KubernetesClient/IKubernetes.cs new file mode 100644 index 0000000..5fa42e9 --- /dev/null +++ b/src/KubernetesClient/IKubernetes.cs @@ -0,0 +1,9 @@ +namespace k8s; + +public partial interface IKubernetes : IDisposable +{ + /// + /// The base URI of the service. + /// + Uri BaseUri { get; set; } +} diff --git a/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs b/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs index 9bce216..ee29d82 100644 --- a/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs +++ b/src/KubernetesClient/KubeConfigModels/ExecCredentialResponse.cs @@ -4,12 +4,12 @@ namespace k8s.KubeConfigModels { public class ExecStatus { - #nullable enable +#nullable enable public DateTime? Expiry { get; set; } public string? Token { get; set; } public string? ClientCertificateData { get; set; } public string? ClientKeyData { get; set; } - #nullable disable +#nullable disable public bool IsValid() { diff --git a/src/KubernetesClient/Kubernetes.ConfigInit.cs b/src/KubernetesClient/Kubernetes.ConfigInit.cs index e3213fb..72a2d22 100644 --- a/src/KubernetesClient/Kubernetes.ConfigInit.cs +++ b/src/KubernetesClient/Kubernetes.ConfigInit.cs @@ -7,7 +7,7 @@ using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Threading; using k8s.Exceptions; -using Microsoft.Rest; +using k8s.Autorest; namespace k8s { @@ -20,43 +20,6 @@ namespace k8s /// timeout public TimeSpan HttpClientTimeout { get; set; } = TimeSpan.FromSeconds(100); - /// - /// Initializes a new instance of the class. - /// - /// - /// The kube config to use. - /// - /// - /// The to use for all requests. - /// - public Kubernetes(KubernetesClientConfiguration config, HttpClient httpClient) - : this(config, httpClient, false) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The kube config to use. - /// - /// - /// The to use for all requests. - /// - /// - /// Whether or not the object should own the lifetime of . - /// - public Kubernetes(KubernetesClientConfiguration config, HttpClient httpClient, bool disposeHttpClient) - : this( - httpClient, disposeHttpClient) - { - ValidateConfig(config); - CaCerts = config.SslCaCerts; - SkipTlsVerify = config.SkipTlsVerify; - ClientCert = CertUtils.GetClientCert(config); - SetCredentials(config); - } - /// /// Initializes a new instance of the class. /// @@ -105,7 +68,11 @@ namespace k8s { if (config.SkipTlsVerify) { +#if NET5_0_OR_GREATER + HttpClientHandler.SslOptions.RemoteCertificateValidationCallback = +#else HttpClientHandler.ServerCertificateCustomValidationCallback = +#endif (sender, certificate, chain, sslPolicyErrors) => true; } else @@ -115,7 +82,11 @@ namespace k8s throw new KubeConfigException("A CA must be set when SkipTlsVerify === false"); } +#if NET5_0_OR_GREATER + HttpClientHandler.SslOptions.RemoteCertificateValidationCallback = +#else HttpClientHandler.ServerCertificateCustomValidationCallback = +#endif (sender, certificate, chain, sslPolicyErrors) => { return CertificateValidationCallBack(sender, CaCerts, certificate, chain, @@ -126,122 +97,44 @@ namespace k8s // set credentails for the kubernetes client SetCredentials(config); - config.AddCertificates(HttpClientHandler); + + var clientCert = CertUtils.GetClientCert(config); + if (clientCert != null) + { +#if NET5_0_OR_GREATER + HttpClientHandler.SslOptions.ClientCertificates.Add(clientCert); +#else + HttpClientHandler.ClientCertificates.Add(clientCert); +#endif + } } private X509Certificate2Collection CaCerts { get; } + private X509Certificate2 ClientCert { get; } + private bool SkipTlsVerify { get; } - private void CustomInitialize() - { - } - - /// A that simply forwards a request with no further processing. - private sealed class ForwardingHandler : DelegatingHandler - { - public ForwardingHandler(HttpMessageHandler handler) - : base(handler) - { - } - } - - private void AppendDelegatingHandler() - where T : DelegatingHandler, new() - { - var cur = FirstMessageHandler as DelegatingHandler; - - while (cur != null) - { - var next = cur.InnerHandler as DelegatingHandler; - - if (next == null) - { - // last one - // append watcher handler between to last handler - cur.InnerHandler = new T { InnerHandler = cur.InnerHandler }; - break; - } - - cur = next; - } - } - // 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 // of what requests have failed. it considers everything outside 2xx to be failed, including 1xx (e.g. 101 Switching Protocols) and // 3xx. in particular, this prevents upgraded connections and certain generic/custom requests from working. private void CreateHttpClient(DelegatingHandler[] handlers, KubernetesClientConfiguration config) { - FirstMessageHandler = HttpClientHandler = CreateRootHandler(); - - #if NET5_0_OR_GREATER - // https://github.com/kubernetes-client/csharp/issues/587 - // let user control if tcp keep alive until better fix - if (config.TcpKeepAlive) + FirstMessageHandler = HttpClientHandler = new SocketsHttpHandler { - // https://github.com/kubernetes-client/csharp/issues/533 - // net5 only - // this is a temp fix to attach SocketsHttpHandler to HttpClient in order to set SO_KEEPALIVE - // https://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/ - // - // _underlyingHandler is not a public accessible field - // src of net5 HttpClientHandler and _underlyingHandler field defined here - // https://github.com/dotnet/runtime/blob/79ae74f5ca5c8a6fe3a48935e85bd7374959c570/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs#L22 - // - // Should remove after better solution + KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests, + KeepAlivePingDelay = TimeSpan.FromMinutes(3), + KeepAlivePingTimeout = TimeSpan.FromSeconds(30), + }; - var sh = new SocketsHttpHandler - { - KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests, - KeepAlivePingDelay = TimeSpan.FromMinutes(3), - KeepAlivePingTimeout = TimeSpan.FromSeconds(30), - }; - sh.ConnectCallback = async (context, token) => - { - var socket = new Socket(SocketType.Stream, ProtocolType.Tcp) - { - NoDelay = true, - }; - - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); - - var host = context.DnsEndPoint.Host; - - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // https://github.com/dotnet/runtime/issues/24917 - // GetHostAddresses will return {host} if host is an ip - var ips = Dns.GetHostAddresses(host); - if (ips.Length == 0) - { - throw new Exception($"{host} DNS lookup failed"); - } - - host = ips[new Random().Next(ips.Length)].ToString(); - } - - await socket.ConnectAsync(host, context.DnsEndPoint.Port, token).ConfigureAwait(false); - return new NetworkStream(socket, ownsSocket: true); - }; - - - // set HttpClientHandler's cert callback before replace _underlyingHandler - // force HttpClientHandler use our callback - InitializeFromConfig(config); - - var p = HttpClientHandler.GetType().GetField("_underlyingHandler", BindingFlags.NonPublic | BindingFlags.Instance); - p.SetValue(HttpClientHandler, (sh)); - } + HttpClientHandler.SslOptions.ClientCertificates = new X509Certificate2Collection(); +#else + FirstMessageHandler = HttpClientHandler = new HttpClientHandler(); #endif - if (handlers == null || handlers.Length == 0) - { - // ensure we have at least one DelegatingHandler so AppendDelegatingHandler will work - FirstMessageHandler = new ForwardingHandler(HttpClientHandler); - } - else + if (handlers != null) { for (int i = handlers.Length - 1; i >= 0; i--) { diff --git a/src/KubernetesClient/Kubernetes.Exec.cs b/src/KubernetesClient/Kubernetes.Exec.cs index 90dd241..240d8cc 100644 --- a/src/KubernetesClient/Kubernetes.Exec.cs +++ b/src/KubernetesClient/Kubernetes.Exec.cs @@ -1,5 +1,5 @@ using k8s.Models; -using Microsoft.Rest; +using k8s.Autorest; using System.IO; using System.Threading; using System.Threading.Tasks; diff --git a/src/KubernetesClient/Kubernetes.WebSocket.cs b/src/KubernetesClient/Kubernetes.WebSocket.cs index 78123b7..4114c4d 100644 --- a/src/KubernetesClient/Kubernetes.WebSocket.cs +++ b/src/KubernetesClient/Kubernetes.WebSocket.cs @@ -1,5 +1,5 @@ using k8s.Models; -using Microsoft.Rest; +using k8s.Autorest; using System.Net; using System.Net.Http; using System.Net.WebSockets; @@ -237,7 +237,11 @@ namespace k8s if (this.HttpClientHandler != null) { +#if NET5_0_OR_GREATER + foreach (var cert in this.HttpClientHandler.SslOptions.ClientCertificates.OfType()) +#else foreach (var cert in this.HttpClientHandler.ClientCertificates.OfType()) +#endif { webSocketBuilder.AddClientCertificate(cert); } diff --git a/src/KubernetesClient/Kubernetes.cs b/src/KubernetesClient/Kubernetes.cs index ecd97d0..e65e30d 100644 --- a/src/KubernetesClient/Kubernetes.cs +++ b/src/KubernetesClient/Kubernetes.cs @@ -3,7 +3,7 @@ using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Microsoft.Rest; +using k8s.Autorest; namespace k8s { @@ -29,197 +29,40 @@ namespace k8s /// public ServiceClientCredentials Credentials { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// - /// HttpClient to be used - /// - /// - /// True: will dispose the provided httpClient on calling Kubernetes.Dispose(). False: will not dispose provided httpClient - protected Kubernetes(HttpClient httpClient, bool disposeHttpClient) - : base(httpClient, disposeHttpClient) + public HttpClient HttpClient { get; protected set; } + + private IEnumerable HttpMessageHandlers { - Initialize(); + get + { + var handler = FirstMessageHandler; + + while (handler != null) + { + yield return handler; + + DelegatingHandler delegating = handler as DelegatingHandler; + handler = delegating != null ? delegating.InnerHandler : null; + } + } } /// - /// Initializes a new instance of the class. + /// Reference to the first HTTP handler (which is the start of send HTTP + /// pipeline). /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - protected Kubernetes(params DelegatingHandler[] handlers) - : base(handlers) - { - Initialize(); - } + private HttpMessageHandler FirstMessageHandler { get; set; } /// - /// Initializes a new instance of the class. + /// Reference to the innermost HTTP handler (which is the end of send HTTP + /// pipeline). /// - /// - /// Optional. The http client handler used to handle http transport. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - protected Kubernetes(HttpClientHandler rootHandler, params DelegatingHandler[] handlers) - : base(rootHandler, handlers) - { - Initialize(); - } +#if NET5_0_OR_GREATER + private SocketsHttpHandler HttpClientHandler { get; set; } +#else + private HttpClientHandler HttpClientHandler { get; set; } +#endif - /// - /// Initializes a new instance of the class. - /// - /// - /// Optional. The base URI of the service. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - protected Kubernetes(Uri baseUri, params DelegatingHandler[] handlers) - : this(handlers) - { - BaseUri = baseUri ?? throw new ArgumentNullException(nameof(baseUri)); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Optional. The base URI of the service. - /// - /// - /// Optional. The http client handler used to handle http transport. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - protected Kubernetes(Uri baseUri, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) - : this(rootHandler, handlers) - { - BaseUri = baseUri ?? throw new ArgumentNullException(nameof(baseUri)); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Required. Subscription credentials which uniquely identify client subscription. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - public Kubernetes(ServiceClientCredentials credentials, params DelegatingHandler[] handlers) - : this(handlers) - { - Credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); - Credentials.InitializeServiceClient(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Required. Subscription credentials which uniquely identify client subscription. - /// - /// - /// HttpClient to be used - /// - /// - /// True: will dispose the provided httpClient on calling Kubernetes.Dispose(). False: will not dispose provided httpClient - /// - /// Thrown when a required parameter is null - /// - [Obsolete] - public Kubernetes(ServiceClientCredentials credentials, HttpClient httpClient, bool disposeHttpClient) - : this(httpClient, disposeHttpClient) - { - Credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); - Credentials.InitializeServiceClient(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Required. Subscription credentials which uniquely identify client subscription. - /// - /// - /// Optional. The http client handler used to handle http transport. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - public Kubernetes(ServiceClientCredentials credentials, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) - : this(rootHandler, handlers) - { - Credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); - Credentials.InitializeServiceClient(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Optional. The base URI of the service. - /// - /// - /// Required. Subscription credentials which uniquely identify client subscription. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - public Kubernetes(Uri baseUri, ServiceClientCredentials credentials, params DelegatingHandler[] handlers) - : this(handlers) - { - BaseUri = baseUri ?? throw new ArgumentNullException(nameof(baseUri)); - Credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); - Credentials.InitializeServiceClient(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Optional. The base URI of the service. - /// - /// - /// Required. Subscription credentials which uniquely identify client subscription. - /// - /// - /// Optional. The http client handler used to handle http transport. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - public Kubernetes(Uri baseUri, ServiceClientCredentials credentials, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) - : this(rootHandler, handlers) - { - BaseUri = baseUri ?? throw new ArgumentNullException(nameof(baseUri)); - Credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); - Credentials.InitializeServiceClient(this); - } /// /// Initializes client properties. @@ -227,7 +70,6 @@ namespace k8s private void Initialize() { BaseUri = new Uri("http://localhost"); - CustomInitialize(); } private async Task> CreateResultAsync(HttpRequestMessage httpRequest, HttpResponseMessage httpResponse, bool? watch, CancellationToken cancellationToken) @@ -250,11 +92,11 @@ namespace k8s result.Body = KubernetesJson.Deserialize(stream); } } - catch (JsonException ex) + catch (JsonException) { httpRequest.Dispose(); httpResponse.Dispose(); - throw new SerializationException("Unable to deserialize the response.", ex); + throw; } return result; @@ -370,5 +212,37 @@ namespace k8s return httpResponse; } + + /// + /// Indicates whether the ServiceClient has been disposed. + /// + private bool _disposed; + + /// + /// Dispose the ServiceClient. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose the HttpClient and Handlers. + /// + /// True to release both managed and unmanaged resources; false to releases only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + _disposed = true; + + // Dispose the client + HttpClient?.Dispose(); + HttpClient = null; + FirstMessageHandler = null; + HttpClientHandler = null; + } + } } } diff --git a/src/KubernetesClient/KubernetesClient.csproj b/src/KubernetesClient/KubernetesClient.csproj index ba7c21e..fe29e77 100644 --- a/src/KubernetesClient/KubernetesClient.csproj +++ b/src/KubernetesClient/KubernetesClient.csproj @@ -46,7 +46,6 @@ - diff --git a/src/KubernetesClient/KubernetesClientConfiguration.HttpClientHandler.cs b/src/KubernetesClient/KubernetesClientConfiguration.HttpClientHandler.cs deleted file mode 100644 index c07f17f..0000000 --- a/src/KubernetesClient/KubernetesClientConfiguration.HttpClientHandler.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Net.Http; - -namespace k8s -{ - public partial class KubernetesClientConfiguration - { - public HttpClientHandler CreateDefaultHttpClientHandler() - { - var httpClientHandler = new HttpClientHandler(); - - var uriScheme = new Uri(this.Host).Scheme; - - if (uriScheme == "https") - { - if (SkipTlsVerify) - { - httpClientHandler.ServerCertificateCustomValidationCallback = - (sender, certificate, chain, sslPolicyErrors) => true; - } - else - { - httpClientHandler.ServerCertificateCustomValidationCallback = - (sender, certificate, chain, sslPolicyErrors) => - { - return Kubernetes.CertificateValidationCallBack(sender, SslCaCerts, certificate, chain, - sslPolicyErrors); - }; - } - } - - AddCertificates(httpClientHandler); - - return httpClientHandler; - } - - public void AddCertificates(HttpClientHandler handler) - { - if (handler == null) - { - throw new ArgumentNullException(nameof(handler)); - } - - var clientCert = CertUtils.GetClientCert(this); - if (clientCert != null) - { - handler.ClientCertificates.Add(clientCert); - } - } - } -} diff --git a/src/KubernetesClient/KubernetesClientConfiguration.cs b/src/KubernetesClient/KubernetesClientConfiguration.cs index 3bfc670..6a00738 100644 --- a/src/KubernetesClient/KubernetesClientConfiguration.cs +++ b/src/KubernetesClient/KubernetesClientConfiguration.cs @@ -1,5 +1,5 @@ using System.Security.Cryptography.X509Certificates; -using Microsoft.Rest; +using k8s.Autorest; namespace k8s { @@ -83,14 +83,6 @@ namespace k8s /// The access token. public ITokenProvider TokenProvider { get; set; } - /// - /// Set true to enable tcp keep alive - /// You have to set https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html as well - /// - /// true or false - public bool TcpKeepAlive { get; set; } = true; - - /// /// Timeout of REST calls to Kubernetes server /// Does not apply to watch related api diff --git a/src/KubernetesClient/KubernetesList.cs b/src/KubernetesClient/KubernetesList.cs index f5f00a5..5282cc3 100644 --- a/src/KubernetesClient/KubernetesList.cs +++ b/src/KubernetesClient/KubernetesList.cs @@ -1,4 +1,4 @@ -using Microsoft.Rest; +using k8s.Autorest; namespace k8s.Models { diff --git a/src/KubernetesClient/LeaderElection/LeaderElector.cs b/src/KubernetesClient/LeaderElection/LeaderElector.cs index 2a74219..df86cbd 100644 --- a/src/KubernetesClient/LeaderElection/LeaderElector.cs +++ b/src/KubernetesClient/LeaderElection/LeaderElector.cs @@ -1,7 +1,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -using Microsoft.Rest; +using k8s.Autorest; namespace k8s.LeaderElection { diff --git a/src/KubernetesClient/LeaderElection/ResourceLock/MetaObjectLock.cs b/src/KubernetesClient/LeaderElection/ResourceLock/MetaObjectLock.cs index 35fe777..0e5e61f 100644 --- a/src/KubernetesClient/LeaderElection/ResourceLock/MetaObjectLock.cs +++ b/src/KubernetesClient/LeaderElection/ResourceLock/MetaObjectLock.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; using k8s.Models; -using Microsoft.Rest; +using k8s.Autorest; namespace k8s.LeaderElection.ResourceLock diff --git a/src/KubernetesClient/Util/Common/Generic/GenericKubernetesApi.cs b/src/KubernetesClient/Util/Common/Generic/GenericKubernetesApi.cs index aec980f..c12235f 100644 --- a/src/KubernetesClient/Util/Common/Generic/GenericKubernetesApi.cs +++ b/src/KubernetesClient/Util/Common/Generic/GenericKubernetesApi.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; using k8s.Models; using k8s.Util.Common.Generic.Options; -using Microsoft.Rest; +using k8s.Autorest; namespace k8s.Util.Common.Generic { @@ -43,13 +43,6 @@ namespace k8s.Util.Common.Generic _client = apiClient ?? new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig()); } - public TimeSpan ClientTimeout => _client.HttpClient.Timeout; - - public void SetClientTimeout(TimeSpan value) - { - _client.HttpClient.Timeout = value; - } - /// /// Get kubernetes object. /// diff --git a/src/KubernetesClient/WatcherExt.cs b/src/KubernetesClient/WatcherExt.cs index 6e1b33e..4cf7a82 100644 --- a/src/KubernetesClient/WatcherExt.cs +++ b/src/KubernetesClient/WatcherExt.cs @@ -1,7 +1,7 @@ using System.IO; using System.Threading.Tasks; using k8s.Exceptions; -using Microsoft.Rest; +using k8s.Autorest; namespace k8s { diff --git a/tests/E2E.Tests/MnikubeTests.cs b/tests/E2E.Tests/MnikubeTests.cs index 92bd8b2..a05e134 100644 --- a/tests/E2E.Tests/MnikubeTests.cs +++ b/tests/E2E.Tests/MnikubeTests.cs @@ -11,7 +11,7 @@ using Json.Patch; using k8s.LeaderElection; using k8s.LeaderElection.ResourceLock; using k8s.Models; -using Microsoft.Rest; +using k8s.Autorest; using Nito.AsyncEx; using Xunit; diff --git a/tests/KubernetesClient.Tests/AuthTests.cs b/tests/KubernetesClient.Tests/AuthTests.cs index 54575ba..fe18565 100644 --- a/tests/KubernetesClient.Tests/AuthTests.cs +++ b/tests/KubernetesClient.Tests/AuthTests.cs @@ -16,7 +16,7 @@ using k8s.Models; using k8s.Tests.Mock; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Https; -using Microsoft.Rest; +using k8s.Autorest; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; diff --git a/tests/KubernetesClient.Tests/KubernetesExecTests.cs b/tests/KubernetesClient.Tests/KubernetesExecTests.cs index 61d1470..30ddc22 100644 --- a/tests/KubernetesClient.Tests/KubernetesExecTests.cs +++ b/tests/KubernetesClient.Tests/KubernetesExecTests.cs @@ -3,7 +3,7 @@ */ using k8s.Tests.Mock; -using Microsoft.Rest; +using k8s.Autorest; using System; using System.Collections.Generic; using System.Threading; @@ -24,13 +24,12 @@ namespace k8s.Tests [Fact] public async Task WebSocketNamespacedPodExecAsync() { - var credentials = new BasicAuthenticationCredentials() + Kubernetes client = new Kubernetes(new KubernetesClientConfiguration() { - UserName = "my-user", + Host = "http://localhost", + Username = "my-user", Password = "my-secret-password", - }; - - Kubernetes client = new Kubernetes(credentials); + }); client.BaseUri = new Uri("http://localhost"); MockWebSocketBuilder mockWebSocketBuilder = new MockWebSocketBuilder(); @@ -71,14 +70,12 @@ namespace k8s.Tests [Fact] public async Task WebSocketNamespacedPodPortForwardAsync() { - var credentials = new BasicAuthenticationCredentials() + Kubernetes client = new Kubernetes(new KubernetesClientConfiguration() { - UserName = "my-user", + Host = "http://localhost", + Username = "my-user", Password = "my-secret-password", - }; - - Kubernetes client = new Kubernetes(credentials); - client.BaseUri = new Uri("http://localhost"); + }); MockWebSocketBuilder mockWebSocketBuilder = new MockWebSocketBuilder(); client.CreateWebSocketBuilder = () => mockWebSocketBuilder; @@ -112,13 +109,12 @@ namespace k8s.Tests [Fact] public async Task WebSocketNamespacedPodAttachAsync() { - var credentials = new BasicAuthenticationCredentials() + Kubernetes client = new Kubernetes(new KubernetesClientConfiguration() { - UserName = "my-user", + Host = "http://localhost", + Username = "my-user", Password = "my-secret-password", - }; - - Kubernetes client = new Kubernetes(credentials); + }); client.BaseUri = new Uri("http://localhost"); MockWebSocketBuilder mockWebSocketBuilder = new MockWebSocketBuilder(); diff --git a/tests/KubernetesClient.Tests/PodExecTests.cs b/tests/KubernetesClient.Tests/PodExecTests.cs index 374e3e1..4ae2db5 100644 --- a/tests/KubernetesClient.Tests/PodExecTests.cs +++ b/tests/KubernetesClient.Tests/PodExecTests.cs @@ -3,7 +3,7 @@ */ using k8s.Models; -using Microsoft.Rest; +using k8s.Autorest; using System; using System.Collections.Generic; using System.Diagnostics; @@ -193,7 +193,7 @@ namespace k8s.Tests muxedStream.Setup(m => m.GetStream(ChannelIndex.Error, null)).Returns(errorStream); var kubernetesMock = new Moq.Mock( - new object[] { Moq.Mock.Of(), new DelegatingHandler[] { } }) + new object[] { new KubernetesClientConfiguration() { Host = "http://localhost" }, new DelegatingHandler[] { } }) { CallBase = true }; var command = new string[] { "/bin/bash", "-c", "echo Hello, World!" }; @@ -216,7 +216,7 @@ namespace k8s.Tests public async Task NamespacedPodExecAsyncHttpExceptionWithStatus() { var kubernetesMock = new Moq.Mock( - new object[] { Moq.Mock.Of(), new DelegatingHandler[] { } }) + new object[] { new KubernetesClientConfiguration() { Host = "http://localhost" }, new DelegatingHandler[] { } }) { CallBase = true }; var command = new string[] { "/bin/bash", "-c", "echo Hello, World!" }; var handler = new ExecAsyncCallback((stdIn, stdOut, stdError) => Task.CompletedTask); @@ -241,7 +241,7 @@ namespace k8s.Tests public async Task NamespacedPodExecAsyncHttpExceptionNoStatus() { var kubernetesMock = new Moq.Mock( - new object[] { Moq.Mock.Of(), new DelegatingHandler[] { } }) + new object[] { new KubernetesClientConfiguration() { Host = "http://localhost" }, new DelegatingHandler[] { } }) { CallBase = true }; var command = new string[] { "/bin/bash", "-c", "echo Hello, World!" }; var handler = new ExecAsyncCallback((stdIn, stdOut, stdError) => Task.CompletedTask); @@ -265,7 +265,7 @@ namespace k8s.Tests public async Task NamespacedPodExecAsyncGenericException() { var kubernetesMock = new Moq.Mock( - new object[] { Moq.Mock.Of(), new DelegatingHandler[] { } }) + new object[] { new KubernetesClientConfiguration() { Host = "http://localhost" }, new DelegatingHandler[] { } }) { CallBase = true }; var command = new string[] { "/bin/bash", "-c", "echo Hello, World!" }; var handler = new ExecAsyncCallback((stdIn, stdOut, stdError) => Task.CompletedTask); @@ -319,7 +319,7 @@ namespace k8s.Tests muxedStream.Setup(m => m.GetStream(ChannelIndex.Error, null)).Returns(errorStream); var kubernetesMock = new Moq.Mock( - new object[] { Moq.Mock.Of(), new DelegatingHandler[] { } }) + new object[] { new KubernetesClientConfiguration() { Host = "http://localhost" }, new DelegatingHandler[] { } }) { CallBase = true }; var command = new string[] { "/bin/bash", "-c", "echo Hello, World!" }; diff --git a/tests/KubernetesClient.Tests/WebSocketTestBase.cs b/tests/KubernetesClient.Tests/WebSocketTestBase.cs index bf4b01d..bbf2b6f 100644 --- a/tests/KubernetesClient.Tests/WebSocketTestBase.cs +++ b/tests/KubernetesClient.Tests/WebSocketTestBase.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Rest; +using k8s.Autorest; using System; using System.IO; using System.Net.WebSockets; @@ -132,7 +132,10 @@ namespace k8s.Tests /// protected virtual Kubernetes CreateTestClient(ServiceClientCredentials credentials = null) { - return new Kubernetes(credentials ?? AnonymousClientCredentials.Instance) { BaseUri = ServerBaseAddress }; + return new Kubernetes(new KubernetesClientConfiguration() + { + Host = ServerBaseAddress.ToString(), + }); } /// diff --git a/version.json b/version.json index 4235c8b..16bca38 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "7.0", + "version": "7.1", "publicReleaseRefSpec": [ "^refs/heads/master$", // we release out of master ],