-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Created kubernetes client from openapi spec #762
Changes from 12 commits
8f2b761
9171020
fa0c7ac
28cd413
bfec669
fa9d864
870019d
f66ed8e
8b46b3d
2925736
13e434e
6317e24
124d9ac
0b86072
55ca735
7ecf114
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
plugins { | ||
id 'io.micronaut.build.internal.kubernetes-module' | ||
id 'io.micronaut.openapi' version libs.versions.micronaut.gradle.plugin | ||
} | ||
|
||
micronautBuild { | ||
binaryCompatibility { | ||
enabled.set(false) | ||
} | ||
} | ||
|
||
micronaut { | ||
openapi { | ||
client(layout.buildDirectory.file("openapi.yaml").get().asFile) { | ||
apiPackageName = "io.micronaut.kubernetes.client.openapi.api" | ||
modelPackageName = "io.micronaut.kubernetes.client.openapi.model" | ||
clientId = "kubernetes-client" | ||
useReactive = false | ||
} | ||
} | ||
} | ||
|
||
dependencies { | ||
annotationProcessor(mnValidation.micronaut.validation.processor) | ||
annotationProcessor(mnSerde.micronaut.serde.processor) | ||
annotationProcessor(mn.micronaut.inject.java) | ||
implementation(mnValidation.micronaut.validation) | ||
implementation(mnSerde.micronaut.serde.jackson) | ||
implementation(mnOpenapi.micronaut.openapi) | ||
compileOnly(mn.micronaut.http.client) | ||
compileOnly(mn.micronaut.json.core) | ||
compileOnly(libs.netty.incubator.codec.http3) // ClientSslBuilderImpl doesn't compile without it | ||
testImplementation(libs.testcontainers.k3s) | ||
testImplementation(mn.micronaut.http.client) | ||
testImplementation(mn.micronaut.http.server.netty) | ||
} | ||
|
||
tasks.register("prepareOpenapiSpec") { | ||
inputs.property("kubernetes-client-version", libs.versions.io.kubernetes.client.java) | ||
def outputFile = layout.buildDirectory.file("openapi.yaml") | ||
doLast { | ||
def clientVersion = inputs.getProperties().get("kubernetes-client-version") | ||
def uri = uri("https://raw.githubusercontent.com/kubernetes-client/java/refs/tags/v${clientVersion}/kubernetes/api/openapi.yaml") | ||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(uri.toURL().openStream())) | ||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile.get().asFile)))) { | ||
println "Downloading kubernetes client spec file: ${uri}" | ||
String line | ||
while ((line = reader.readLine()) != null) { | ||
if (line.contains("x-implements")) { | ||
// skip lines which contains x-implements because we don't want that generated classes implement | ||
// io.kubernetes.client.common.KubernetesObject and io.kubernetes.client.common.KubernetesListObject | ||
reader.readLine() // skip one more line because it contains KubernetesObject interface | ||
} else { | ||
writer.writeLine(line) | ||
} | ||
} | ||
} | ||
} | ||
outputs.file(outputFile) | ||
} | ||
|
||
generateClientOpenApiApis.dependsOn("prepareOpenapiSpec") | ||
generateClientOpenApiModels.dependsOn("prepareOpenapiSpec") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/* | ||
* Copyright 2017-2024 original authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micronaut.kubernetes.client.openapi; | ||
|
||
import io.micronaut.buffer.netty.NettyByteBufferFactory; | ||
import io.micronaut.context.annotation.BootstrapContextCompatible; | ||
import io.micronaut.context.annotation.Context; | ||
import io.micronaut.context.annotation.Factory; | ||
import io.micronaut.context.annotation.Requires; | ||
import io.micronaut.core.annotation.Internal; | ||
import io.micronaut.core.annotation.NonNull; | ||
import io.micronaut.core.convert.ConversionService; | ||
import io.micronaut.core.io.ResourceResolver; | ||
import io.micronaut.http.MediaType; | ||
import io.micronaut.http.bind.DefaultRequestBinderRegistry; | ||
import io.micronaut.http.body.ContextlessMessageBodyHandlerRegistry; | ||
import io.micronaut.http.body.MessageBodyHandlerRegistry; | ||
import io.micronaut.http.client.DefaultHttpClientConfiguration; | ||
import io.micronaut.http.client.LoadBalancer; | ||
import io.micronaut.http.client.filter.ClientFilterResolutionContext; | ||
import io.micronaut.http.client.filter.DefaultHttpClientFilterResolver; | ||
import io.micronaut.http.client.netty.DefaultHttpClient; | ||
import io.micronaut.http.client.netty.NettyClientCustomizer; | ||
import io.micronaut.http.codec.MediaTypeCodecRegistry; | ||
import io.micronaut.http.netty.body.NettyByteBufMessageBodyHandler; | ||
import io.micronaut.http.netty.body.NettyCharSequenceBodyWriter; | ||
import io.micronaut.http.netty.body.NettyJsonHandler; | ||
import io.micronaut.http.netty.body.NettyJsonStreamHandler; | ||
import io.micronaut.http.netty.body.NettyWritableBodyWriter; | ||
import io.micronaut.json.JsonMapper; | ||
import io.micronaut.json.codec.JsonMediaTypeCodec; | ||
import io.micronaut.json.codec.JsonStreamMediaTypeCodec; | ||
import io.micronaut.kubernetes.client.openapi.config.KubeConfigLoader; | ||
import io.micronaut.kubernetes.client.openapi.ssl.KubernetesClientSslBuilder; | ||
import io.micronaut.kubernetes.client.openapi.ssl.KubernetesPrivateKeyLoader; | ||
import io.micronaut.kubernetes.client.openapi.config.KubeConfig; | ||
import io.micronaut.kubernetes.client.openapi.config.KubernetesClientConfiguration; | ||
import io.micronaut.runtime.ApplicationConfiguration; | ||
import io.micronaut.websocket.context.WebSocketBeanRegistry; | ||
import io.netty.channel.Channel; | ||
import io.netty.channel.MultithreadEventLoopGroup; | ||
import io.netty.channel.socket.nio.NioDatagramChannel; | ||
import io.netty.channel.socket.nio.NioSocketChannel; | ||
import io.netty.util.concurrent.DefaultThreadFactory; | ||
import jakarta.inject.Named; | ||
import jakarta.inject.Singleton; | ||
|
||
import java.net.URI; | ||
import java.util.Collections; | ||
|
||
/** | ||
* Factory for kubernetes http client. | ||
*/ | ||
@Factory | ||
@Context | ||
@Internal | ||
@BootstrapContextCompatible | ||
@Requires(beans = KubernetesClientConfiguration.class) | ||
final class KubernetesHttpClientFactory { | ||
static final String CLIENT_ID = "kubernetes-client"; | ||
|
||
private final KubeConfig kubeConfig; | ||
private final KubernetesPrivateKeyLoader kubernetesPrivateKeyLoader; | ||
private final ResourceResolver resourceResolver; | ||
private final DefaultHttpClientFilterResolver defaultHttpClientFilterResolver; | ||
|
||
KubernetesHttpClientFactory(KubeConfigLoader kubeConfigLoader, | ||
KubernetesPrivateKeyLoader kubernetesPrivateKeyLoader, | ||
ResourceResolver resourceResolver, | ||
DefaultHttpClientFilterResolver defaultHttpClientFilterResolver) { | ||
kubeConfig = kubeConfigLoader.getKubeConfig(); | ||
this.kubernetesPrivateKeyLoader = kubernetesPrivateKeyLoader; | ||
this.resourceResolver = resourceResolver; | ||
this.defaultHttpClientFilterResolver = defaultHttpClientFilterResolver; | ||
} | ||
|
||
@Singleton | ||
@Named(CLIENT_ID) | ||
@BootstrapContextCompatible | ||
protected DefaultHttpClient getKubernetesHttpClient() { | ||
URI uri = URI.create(kubeConfig.getCluster().server()); | ||
|
||
return new DefaultHttpClient(LoadBalancer.fixed(uri), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will replace this with http client builder once micronaut-core 4.7.0 gets released There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this code now use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
null, | ||
new DefaultHttpClientConfiguration(), | ||
null, | ||
defaultHttpClientFilterResolver, | ||
defaultHttpClientFilterResolver.resolveFilterEntries(new ClientFilterResolutionContext(Collections.singletonList(CLIENT_ID), null)), | ||
new DefaultThreadFactory(MultithreadEventLoopGroup.class), | ||
new KubernetesClientSslBuilder(resourceResolver, kubeConfig, kubernetesPrivateKeyLoader), | ||
createDefaultMediaTypeRegistry(), | ||
createDefaultMessageBodyHandlerRegistry(), | ||
WebSocketBeanRegistry.EMPTY, | ||
new DefaultRequestBinderRegistry(ConversionService.SHARED), | ||
null, | ||
NioSocketChannel::new, | ||
NioDatagramChannel::new, | ||
new NettyClientCustomizer() { | ||
@Override | ||
public @NonNull NettyClientCustomizer specializeForChannel(@NonNull Channel channel, @NonNull ChannelRole role) { | ||
return NettyClientCustomizer.super.specializeForChannel(channel, role); | ||
} | ||
}, | ||
null, | ||
ConversionService.SHARED, | ||
null); | ||
} | ||
|
||
private static MediaTypeCodecRegistry createDefaultMediaTypeRegistry() { | ||
JsonMapper mapper = JsonMapper.createDefault(); | ||
ApplicationConfiguration configuration = new ApplicationConfiguration(); | ||
return MediaTypeCodecRegistry.of( | ||
new JsonMediaTypeCodec(mapper, configuration, null), | ||
new JsonStreamMediaTypeCodec(mapper, configuration, null) | ||
); | ||
} | ||
|
||
private static MessageBodyHandlerRegistry createDefaultMessageBodyHandlerRegistry() { | ||
ApplicationConfiguration applicationConfiguration = new ApplicationConfiguration(); | ||
ContextlessMessageBodyHandlerRegistry registry = new ContextlessMessageBodyHandlerRegistry( | ||
applicationConfiguration, | ||
NettyByteBufferFactory.DEFAULT, | ||
new NettyByteBufMessageBodyHandler(), | ||
new NettyWritableBodyWriter(applicationConfiguration) | ||
); | ||
JsonMapper mapper = JsonMapper.createDefault(); | ||
registry.add(MediaType.APPLICATION_JSON_TYPE, new NettyJsonHandler<>(mapper)); | ||
registry.add(MediaType.APPLICATION_JSON_TYPE, new NettyCharSequenceBodyWriter()); | ||
registry.add(MediaType.APPLICATION_JSON_STREAM_TYPE, new NettyJsonStreamHandler<>(mapper)); | ||
return registry; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would like to see how we can avoid this logic and recreating all these objects? //cc @yawkat There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All those will be removed once micronaut-core 4.7.0 gets released since that version contains http client builder implementation |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* Copyright 2017-2024 original authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micronaut.kubernetes.client.openapi; | ||
|
||
import io.micronaut.context.annotation.Requires; | ||
import io.micronaut.core.annotation.Internal; | ||
import io.micronaut.core.util.StringUtils; | ||
import io.micronaut.http.MutableHttpRequest; | ||
import io.micronaut.http.annotation.ClientFilter; | ||
import io.micronaut.http.annotation.RequestFilter; | ||
import io.micronaut.kubernetes.client.openapi.config.KubeConfig; | ||
import io.micronaut.kubernetes.client.openapi.config.KubeConfigLoader; | ||
import io.micronaut.kubernetes.client.openapi.config.KubernetesClientConfiguration; | ||
import io.micronaut.kubernetes.client.openapi.config.model.AuthInfo; | ||
import io.micronaut.kubernetes.client.openapi.credential.KubernetesCredentialLoader; | ||
|
||
/** | ||
* Filter which sets the authorization request header with basic or bearer token | ||
* if the client certificate authentication is not enabled. | ||
*/ | ||
@ClientFilter(serviceId = KubernetesHttpClientFactory.CLIENT_ID) | ||
@Requires(beans = KubernetesClientConfiguration.class) | ||
@Internal | ||
final class KubernetesHttpClientFilter { | ||
|
||
private final KubeConfig kubeConfig; | ||
private final KubernetesCredentialLoader kubernetesCredentialLoader; | ||
|
||
KubernetesHttpClientFilter(KubeConfigLoader kubeConfigLoader, | ||
KubernetesCredentialLoader kubernetesCredentialLoader) { | ||
kubeConfig = kubeConfigLoader.getKubeConfig(); | ||
this.kubernetesCredentialLoader = kubernetesCredentialLoader; | ||
} | ||
|
||
@RequestFilter | ||
void doFilter(MutableHttpRequest<?> request) { | ||
AuthInfo user = kubeConfig.getUser(); | ||
if (user.clientCertificateData() != null && user.clientKeyData() != null) { | ||
return; | ||
} | ||
if (StringUtils.isNotEmpty(user.username()) && StringUtils.isNotEmpty(user.password())) { | ||
request.basicAuth(user.username(), user.password()); | ||
return; | ||
} | ||
String token = kubernetesCredentialLoader.getToken(); | ||
if (StringUtils.isEmpty(token)) { | ||
token = user.token(); | ||
} | ||
if (StringUtils.isNotEmpty(token)) { | ||
request.bearerAuth(token); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@melix can you review this Gradle logic?