Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions docs/features/kubernetes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The first thing you need to do is install the `package`_ that provides |kubernet
Install-Package Ocelot.Provider.Kubernetes

``AddKubernetes(bool)`` method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
------------------------------

.. code-block:: csharp
:emphasize-lines: 3
Expand Down Expand Up @@ -84,19 +84,40 @@ If you have services deployed in Kubernetes, you will normally use the naming se
};
builder.Services
.AddOptions<KubeClientOptions>()
.Configure(configureKubeClient); // mannual binding options via IOptions<KubeClientOptions>
.Configure(configureKubeClient); // manual binding options via IOptions<KubeClientOptions>
builder.Services
.AddOcelot(builder.Configuration)
.AddKubernetes(false); // don't use pod service account, and IOptions<KubeClientOptions> is reused

.. _break: http://break.do

**Note**, this could also be written like this (shortened version):

.. code-block:: csharp
:emphasize-lines: 2, 10

builder.Services
.AddKubeClientOptions(opts =>
{
opts.ApiEndPoint = new UriBuilder("https", "my-host", 443).Uri;
opts.AuthStrategy = KubeAuthStrategy.BearerToken;
opts.AccessToken = "my-token";
opts.AllowInsecure = true;
})
.AddOcelot(builder.Configuration)
.AddKubernetes(false); // don't use pod service account, and client options provided via AddKubeClientOptions

Finally, it creates the `KubeClient`_ from your options.

**Note**: For understanding the ``IOptions<TOptions>`` interface, please refer to the Microsoft Learn documentation: `Options pattern in .NET <https://learn.microsoft.com/en-us/dotnet/core/extensions/options>`_.
**Note 1**: For understanding the ``IOptions<TOptions>`` interface, please refer to the Microsoft Learn documentation: `Options pattern in .NET <https://learn.microsoft.com/en-us/dotnet/core/extensions/options>`_.

**Note 2**: Please consider this Case 2 as an example of manual setup when you **do not** use a pod service account.
We recommend using our official extension method, which receives an ``Action<KubeClientOptions>`` argument with your options: refer to the :ref:`k8s-addkubernetes-action-method` below.

.. _k8s-addkubernetes-action-method:

``AddKubernetes(Action<KubeClientOptions>)`` method [#f2]_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
----------------------------------------------------------

.. code-block:: csharp
:emphasize-lines: 3
Expand Down
19 changes: 16 additions & 3 deletions samples/Kubernetes/ApiGateway/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
.SetBasePath(builder.Environment.ContentRootPath)
.AddOcelot();

goto Case4; // Your case should be selected here!!!
goto Case5; // Your case should be selected here!!!

// Link: https://github.com/ThreeMammals/Ocelot/blob/develop/docs/features/kubernetes.rst#addkubernetes-bool-method
Case1: // Use a pod service account
Expand All @@ -36,7 +36,7 @@
goto Start;

// Link: https://github.com/ThreeMammals/Ocelot/blob/develop/docs/features/kubernetes.rst#addkubernetes-action-kubeclientoptions-method
Case3: // Use global ServiceDiscoveryProvider json-options
Case3: // Don't use a pod service account, manually bind options, ignore global ServiceDiscoveryProvider json-options
Action<KubeClientOptions> myOptions = opts =>
{
opts.ApiEndPoint = new UriBuilder(Uri.UriSchemeHttps, "my-host", 443).Uri;
Expand All @@ -49,8 +49,21 @@
.AddKubernetes(myOptions); // configure options with action, without optional args
goto Start;

Case4: // Don't use a pod service account, manually bind options, ignore global ServiceDiscoveryProvider json-options
builder.Services
.AddKubeClientOptions(opts =>
{
opts.ApiEndPoint = new UriBuilder("https", "my-host", 443).Uri;
opts.AuthStrategy = KubeAuthStrategy.BearerToken;
opts.AccessToken = "my-token";
opts.AllowInsecure = true;
})
.AddOcelot(builder.Configuration)
.AddKubernetes(false); // don't use pod service account, and client options provided via AddKubeClientOptions
goto Start;

// Link: https://github.com/ThreeMammals/Ocelot/blob/develop/docs/features/kubernetes.rst#addkubernetes-action-kubeclientoptions-method
Case4: // Use global ServiceDiscoveryProvider json-options
Case5: // Use global ServiceDiscoveryProvider json-options
Action<KubeClientOptions>? none = null;
builder.Services
.AddOcelot(builder.Configuration)
Expand Down
16 changes: 6 additions & 10 deletions src/Ocelot.Provider.Kubernetes/EndPointClientV1.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using HTTPlease;
using KubeClient.Http;
using KubeClient.Models;
using KubeClient.ResourceClients;
using Ocelot.Provider.Kubernetes.Interfaces;
Expand All @@ -7,31 +7,27 @@ namespace Ocelot.Provider.Kubernetes;

public class EndPointClientV1 : KubeResourceClient, IEndPointClient
{
private readonly HttpRequest _collection;
private static readonly HttpRequest Collection = KubeRequest.Create("api/v1/namespaces/{Namespace}/endpoints/{ServiceName}");

public EndPointClientV1(IKubeApiClient client) : base(client)
{
_collection = KubeRequest.Create("api/v1/namespaces/{Namespace}/endpoints/{ServiceName}");
}

public async Task<EndpointsV1> GetAsync(string serviceName, string kubeNamespace = null, CancellationToken cancellationToken = default)
public Task<EndpointsV1> GetAsync(string serviceName, string kubeNamespace = null, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(serviceName))
{
throw new ArgumentNullException(nameof(serviceName));
}

var request = _collection
var request = Collection
.WithTemplateParameters(new
{
Namespace = kubeNamespace ?? KubeClient.DefaultNamespace,
ServiceName = serviceName,
});

var response = await Http.GetAsync(request, cancellationToken);

return response.IsSuccessStatusCode
? await response.ReadContentAsAsync<EndpointsV1>()
: null;
return Http.GetAsync(request, cancellationToken)
.ReadContentAsObjectV1Async<EndpointsV1>(operationDescription: $"get {nameof(EndpointsV1)}");
}
}
43 changes: 40 additions & 3 deletions src/Ocelot.Provider.Kubernetes/Kube.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace Ocelot.Provider.Kubernetes;
/// </remarks>
public class Kube : IServiceDiscoveryProvider
{
private static readonly (string ResourceKind, string ResourceApiVersion) EndPointsKubeKind = KubeObjectV1.GetKubeKind<EndpointsV1>();

private readonly KubeRegistryConfiguration _configuration;
private readonly IOcelotLogger _logger;
private readonly IKubeApiClient _kubeApi;
Expand Down Expand Up @@ -46,9 +48,44 @@ public virtual async Task<List<Service>> GetAsync()
.ToList();
}

private Task<EndpointsV1> GetEndpoint() => _kubeApi
.ResourceClient<IEndPointClient>(client => new EndPointClientV1(client))
.GetAsync(_configuration.KeyOfServiceInK8s, _configuration.KubeNamespace);
private string Message(string details)
=> $"Failed to retrieve {EndPointsKubeKind.ResourceApiVersion}/{EndPointsKubeKind.ResourceKind} '{_configuration.KeyOfServiceInK8s}' in namespace '{_configuration.KubeNamespace}': {details}";

private async Task<EndpointsV1> GetEndpoint()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved this logic here because it keeps the resource-client implementation cleaner (since it wouldn't otherwise be clear to someone who's not seen this code before what the lifetime of the resource client is, vs the lifetime of the containing type or its logger).

{
try
{
return await _kubeApi
.ResourceClient<IEndPointClient>(client => new EndPointClientV1(client))
.GetAsync(_configuration.KeyOfServiceInK8s, _configuration.KubeNamespace);
}
catch (KubeApiException ex)
{
string Msg()
{
StatusV1 status = ex.Status;
string httpStatusCode = "-"; // Unknown
if (ex.InnerException is HttpRequestException e)
{
httpStatusCode = e.StatusCode.ToString();
}

return Message($"(HTTP.{httpStatusCode}/{status.Status}/{status.Reason}): {status.Message}");
}

_logger.LogError(Msg, ex);
}
catch (HttpRequestException ex)
{
_logger.LogError(() => Message($"({ex.HttpRequestError}/HTTP.{ex.StatusCode})."), ex);
}
catch (Exception unexpected)
{
_logger.LogError(() => Message($"(an unexpected ex occurred)."), unexpected);
}

return null;
}

private bool CheckErroneousState(EndpointsV1 endpoint)
=> (endpoint?.Subsets?.Count ?? 0) == 0; // null or count is zero
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
<NoWarn>1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="KubeClient" Version="2.5.12" />
<PackageReference Include="KubeClient.Extensions.DependencyInjection" Version="2.5.12" />
<PackageReference Include="KubeClient" Version="3.0.1" />
<PackageReference Include="KubeClient.Extensions.DependencyInjection" Version="3.0.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
18 changes: 9 additions & 9 deletions test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,21 @@
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.3" />
</ItemGroup>
<!-- Conditionally obtain references for the net 8.0 target -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="8.0.12" />
</ItemGroup>
<!-- Conditionally obtain references for the net 9.0 target -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="9.0.1" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="9.0.3" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using KubeClient;
using KubeClient.Models;
using KubeClient.ResourceClients;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand Down Expand Up @@ -324,7 +325,7 @@ private void GivenThereIsAFakeKubernetesProvider(EndpointsV1 endpoints, bool isS
}

endpoints.Metadata.Generation = _k8sServiceGeneration;
json = JsonConvert.SerializeObject(endpoints);
json = JsonConvert.SerializeObject(endpoints, KubeResourceClient.SerializerSettings);
}

if (context.Request.Headers.TryGetValue("Authorization", out var values))
Expand Down
18 changes: 9 additions & 9 deletions test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@
<PackageReference Include="Shouldly" Version="4.2.1" />
<PackageReference Include="IdentityServer4" Version="4.1.2" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
<PackageReference Include="Microsoft.Data.SQLite" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.1" />
<PackageReference Include="Microsoft.Data.SQLite" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.3" />
</ItemGroup>
</Project>
Loading
Loading