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
7 changes: 6 additions & 1 deletion src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,16 @@ The default resolver options values are loaded from the pod environment

You can override these settings.

You can deal with ephemeral tokens using the {@link io.vertx.serviceresolver.kube.KubeResolver#tokenProvider}.

[source,java]
----
{@link examples.ServiceResolverExamples#configuringKubernetesResolver}
{@link examples.ServiceResolverExamples#usingKubernetesTokenProvider}
----

The resolver calls the provider when it needs a fresh token. The token is cached by the resolver until it is detetected
stale by the resolver (upon a `401` server response code).

=== SRV resolver

The SRV resolver uses DNS SRV records to resolve and locate services.
Expand Down
19 changes: 18 additions & 1 deletion src/main/java/examples/ServiceResolverExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.vertx.serviceresolver.srv.SrvResolverOptions;

import java.util.List;
import java.util.function.Supplier;

@Source
public class ServiceResolverExamples {
Expand Down Expand Up @@ -108,7 +109,23 @@ public void usingKubernetesResolver(Vertx vertx) {

KubeResolverOptions options = new KubeResolverOptions();

AddressResolver resolver = KubeResolver.create(options);
KubeResolver resolver = KubeResolver.create(options);

HttpClient client = vertx.httpClientBuilder()
.withAddressResolver(resolver)
.build();
}

private static String loadToken() {
return "the-token";
}

public void usingKubernetesTokenProvider(Vertx vertx) {

KubeResolverOptions options = new KubeResolverOptions();

KubeResolver resolver = KubeResolver.create(options)
.tokenProvider(() -> loadToken());

HttpClient client = vertx.httpClientBuilder()
.withAddressResolver(resolver)
Expand Down
24 changes: 18 additions & 6 deletions src/main/java/io/vertx/serviceresolver/kube/KubeResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,22 @@
package io.vertx.serviceresolver.kube;

import io.vertx.core.net.AddressResolver;
import io.vertx.serviceresolver.impl.ServiceAddressResolver;
import io.vertx.serviceresolver.kube.impl.KubeResolverImpl;
import io.vertx.serviceresolver.ServiceAddress;
import io.vertx.serviceresolver.kube.impl.KubeAddressResolver;

import java.util.function.Supplier;

/**
* A resolver for services within a Kubernetes cluster.
*/
public interface KubeResolver {
public interface KubeResolver extends AddressResolver<ServiceAddress> {

/**
* Create a Kubernetes resolver with the default options.
*
* @return the resolver
*/
static AddressResolver create() {
static KubeResolver create() {
return create(new KubeResolverOptions());
}

Expand All @@ -33,7 +35,17 @@ static AddressResolver create() {
*
* @return the resolver
*/
static AddressResolver create(KubeResolverOptions options) {
return new ServiceAddressResolver((vertx, lookup) -> new KubeResolverImpl(vertx, options));
static KubeResolver create(KubeResolverOptions options) {
return new KubeAddressResolver(options);
}

/**
* Set a token provider for the resolver: the {@code tokenProvider} supplier is called when the resolver
* needs a token or retries when the server responses with a {@code 401} code.
*
* @param tokenProvider the token provider called when a bearer token is needed
* @return this instance
*/
KubeResolver tokenProvider(Supplier<String> tokenProvider);

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
import io.vertx.core.net.PemTrustOptions;
import io.vertx.core.net.SocketAddress;
import io.vertx.serviceresolver.ServiceResolverOptions;
import io.vertx.serviceresolver.kube.impl.KubeResolverImpl;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.function.Supplier;

import static io.vertx.serviceresolver.kube.impl.KubeResolverImpl.*;

/**
*
Expand All @@ -31,17 +35,11 @@
@JsonGen(publicConverter = false)
public class KubeResolverOptions extends ServiceResolverOptions {

private static final String KUBERNETES_SERVICE_HOST = "KUBERNETES_SERVICE_HOST";
private static final String KUBERNETES_SERVICE_PORT = "KUBERNETES_SERVICE_PORT";
private static final String KUBERNETES_SERVICE_ACCOUNT_TOKEN = "/var/run/secrets/kubernetes.io/serviceaccount/token";
private static final String KUBERNETES_SERVICE_ACCOUNT_CA = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
private static final String KUBERNETES_SERVICE_ACCOUNT_NAMESPACE = "/var/run/secrets/kubernetes.io/serviceaccount/namespace";

private static final SocketAddress DEFAULT_SERVER;
private static final String DEFAULT_TOKEN;
private static final String DEFAULT_NAMESPACE;
private static final HttpClientOptions DEFAULT_HTTP_CLIENT_OPTIONS;
private static final WebSocketClientOptions DEFAULT_WEB_SOCKET_OPTIONS;
public static final SocketAddress DEFAULT_SERVER;
public static final String DEFAULT_TOKEN;
public static final String DEFAULT_NAMESPACE;
public static final HttpClientOptions DEFAULT_HTTP_CLIENT_OPTIONS;
public static final WebSocketClientOptions DEFAULT_WEB_SOCKET_OPTIONS;

static {
String host = System.getenv(KUBERNETES_SERVICE_HOST);
Expand All @@ -53,14 +51,7 @@ public class KubeResolverOptions extends ServiceResolverOptions {
} catch (NumberFormatException ignore) {
}
}
File tokenFile = new File(KUBERNETES_SERVICE_ACCOUNT_TOKEN);
String token = null;
if (tokenFile.exists()) {
try {
token = Buffer.buffer(Files.readAllBytes(tokenFile.toPath())).toString();
} catch (IOException ignore) {
}
}
String token = KubeResolverImpl.defaultToken().orElse(null);
String namespace = "default";
File namespaceFile = new File(KUBERNETES_SERVICE_ACCOUNT_NAMESPACE);
if (namespaceFile.exists()) {
Expand All @@ -84,11 +75,11 @@ public class KubeResolverOptions extends ServiceResolverOptions {
DEFAULT_WEB_SOCKET_OPTIONS = webSocketClientOptions;
}

private SocketAddress server = DEFAULT_SERVER;
private String namespace = DEFAULT_NAMESPACE;
private String bearerToken = DEFAULT_TOKEN;
private HttpClientOptions httpClientOptions = new HttpClientOptions(DEFAULT_HTTP_CLIENT_OPTIONS);
private WebSocketClientOptions webSocketClientOptions = new WebSocketClientOptions(DEFAULT_WEB_SOCKET_OPTIONS);
private SocketAddress server;
private String namespace;
private String bearerToken;
private HttpClientOptions httpClientOptions;
private WebSocketClientOptions webSocketClientOptions;

/**
* Constructor with default options, those might have been set from the pod environment when running in a pod.
Expand All @@ -105,6 +96,7 @@ public KubeResolverOptions() {
* Default constructor.
*/
public KubeResolverOptions(KubeResolverOptions other) {
this();
this.server = other.server;
this.namespace = other.namespace;
this.bearerToken = other.bearerToken;
Expand All @@ -116,6 +108,7 @@ public KubeResolverOptions(KubeResolverOptions other) {
* JSON constructor
*/
public KubeResolverOptions(JsonObject json) {
this();
KubeResolverOptionsConverter.fromJson(json, this);
}

Expand Down Expand Up @@ -146,16 +139,31 @@ public KubeResolverOptions setNamespace(String namespace) {
return this;
}

/**
* @return the bearer token
*/
public String getBearerToken() {
return bearerToken;
}

/**
* <p>Set a bearer token presented by the resolver to the Kubernetes server.</p>
*
* <p>When a dynamic value is required (such as an ephemeral token), prefer using {@link KubeResolver#tokenProvider(Supplier)} instead.</p>
*
* <p>The bearer token value might be loaded from {@code /var/run/secrets/kubernetes.io/serviceaccount/token} when this
* resource exists on the file system, this token will be presented if no other token is configured. This token is subject
* to be reloaded if the server returns a {@code 401} response.</p>
*
* @param bearerToken the bearer token
* @return this object instance
*/
public KubeResolverOptions setBearerToken(String bearerToken) {
this.bearerToken = bearerToken;
return this;
}

public HttpClientOptions getHttpClientOptions() {
public HttpClientOptions getHttpClientOptions() {
return httpClientOptions;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2011-2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.serviceresolver.kube.impl;

import io.vertx.core.Vertx;
import io.vertx.core.net.AddressResolver;
import io.vertx.core.spi.endpoint.EndpointResolver;
import io.vertx.serviceresolver.ServiceAddress;
import io.vertx.serviceresolver.kube.KubeResolver;
import io.vertx.serviceresolver.kube.KubeResolverOptions;

import java.util.function.Supplier;

public class KubeAddressResolver implements KubeResolver {

private final KubeResolverOptions options;
private Supplier<String> tokenProvider;

public KubeAddressResolver(KubeResolverOptions options) {
this.options = options;
}

@Override
public EndpointResolver<ServiceAddress, ?, ?, ?> endpointResolver(Vertx vertx) {
Supplier<String> tokenProvider = this.tokenProvider;
if (tokenProvider == null) {
String token = options.getBearerToken();
if (token != null) {
if (token.equals(KubeResolverOptions.DEFAULT_TOKEN)) {
// Special case
tokenProvider = () -> KubeResolverImpl.defaultToken().orElse(null);
} else {
tokenProvider = () -> token;
}
} else {
tokenProvider = null;
}
}
return new KubeResolverImpl<>(vertx, tokenProvider, options);
}

@Override
public KubeResolver tokenProvider(Supplier<String> tokenProvider) {
this.tokenProvider = tokenProvider;
return this;
}
}
Loading