Skip to content

Commit

Permalink
Merge pull request #1041 from micronaut-projects/andriy/oke-client
Browse files Browse the repository at this point in the history
Create OpenAPI Kubernetes client integration with OKE
  • Loading branch information
andriy-dmytruk authored Dec 13, 2024
2 parents 528d7ee + dbea8db commit 6009a37
Show file tree
Hide file tree
Showing 11 changed files with 708 additions and 0 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ managed-apache-http-core5 = "5.3.1"
micronaut-gradle-plugin = "4.4.4"
micronaut-groovy = "4.5.0"
micronaut-kotlin = "4.5.0"
micronaut-kubernetes = "6.2.1"
micronaut-micrometer = "5.9.3"
micronaut-reactor = "3.6.0"
micronaut-rxjava2 = "2.6.0"
Expand All @@ -41,6 +42,7 @@ micronaut-core = { module = 'io.micronaut:micronaut-core-bom', version.ref = 'mi
# micronaut boms
micronaut-groovy = { module = "io.micronaut.groovy:micronaut-groovy-bom", version.ref = "micronaut-groovy" }
micronaut-kotlin = { module = "io.micronaut.kotlin:micronaut-kotlin-bom", version.ref = "micronaut-kotlin" }
micronaut-kubernetes = { module = "io.micronaut.kubernetes:micronaut-kubernetes-bom", version.ref = "micronaut-kubernetes" }
micronaut-micrometer = { module = "io.micronaut.micrometer:micronaut-micrometer-bom", version.ref = "micronaut-micrometer" }
micronaut-reactor = { module = "io.micronaut.reactor:micronaut-reactor-bom", version.ref = "micronaut-reactor" }
micronaut-rxjava2 = { module = "io.micronaut.rxjava2:micronaut-rxjava2-bom", version.ref = "micronaut-rxjava2" }
Expand Down
28 changes: 28 additions & 0 deletions oraclecloud-oke-kubernetes-client/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
plugins {
id 'io.micronaut.build.internal.oraclecloud-module'
}

dependencies {
api mnKubernetes.micronaut.kubernetes.client.openapi.common
implementation mnValidation.validation
implementation projects.micronautOraclecloudBmcContainerengine

testImplementation mnKubernetes.micronaut.kubernetes.client.openapi
api projects.micronautOraclecloudCommon
testImplementation mn.micronaut.context
testAnnotationProcessor mn.micronaut.inject.java
testImplementation mn.micronaut.inject.java
testImplementation mn.micronaut.inject.groovy
testImplementation mn.micronaut.inject.groovy.test
testImplementation mn.micronaut.http.server.netty
}

tasks.withType(Test).configureEach {
useJUnitPlatform()
}

micronautBuild {
binaryCompatibility {
enabled = false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* 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.oraclecloud.oke.kubernetes.client;

import com.oracle.bmc.containerengine.ContainerEngineClient;
import com.oracle.bmc.containerengine.model.CreateClusterKubeconfigContentDetails;
import com.oracle.bmc.containerengine.model.CreateClusterKubeconfigContentDetails.Endpoint;
import com.oracle.bmc.containerengine.requests.CreateKubeconfigRequest;
import com.oracle.bmc.containerengine.responses.CreateKubeconfigResponse;
import io.micronaut.context.annotation.BootstrapContextCompatible;
import io.micronaut.context.annotation.Replaces;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.io.ResourceResolver;
import io.micronaut.kubernetes.client.openapi.config.AbstractKubeConfigLoader;
import io.micronaut.kubernetes.client.openapi.config.KubeConfig;
import io.micronaut.kubernetes.client.openapi.config.KubeConfigLoader;
import jakarta.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A class for loading a kubeconfig from OKE. It replaces the default kube config loader, which
* reads it from a file.
*
* @since 4.4.x
* @author Andriy Dmytruk
*/
@Singleton
@BootstrapContextCompatible
@Replaces(KubeConfigLoader.class)
@Requires(beans = OkeKubernetesClientConfig.class)
final class OkeKubeConfigLoader extends AbstractKubeConfigLoader {

private static final String TOKEN_VERSION = "2.0.0";

private static final Logger LOG = LoggerFactory.getLogger(OkeKubeConfigLoader.class);
private final ContainerEngineClient client;
private final OkeKubernetesClientConfig config;

/**
* Create Kubeconfig loader.
*
* @param containerEngineClient The client to use to get kubeconfig
* @param config The configuration
* @param resourceResolver The resource resolver.
*/
OkeKubeConfigLoader(
ContainerEngineClient containerEngineClient,
OkeKubernetesClientConfig config,
ResourceResolver resourceResolver
) {
super(resourceResolver);
this.client = containerEngineClient;
this.config = config;
}

@Override
protected @Nullable KubeConfig loadKubeConfig() {
return createCubeConfig(config.clusterId(), config.endpointType());
}

/**
* A method that uses the container engine client to create a kube config and write it to the
* file specified by the environment variable.
*
* @param okeClusterId The cluster ID
* @param endpointType The endpoint type
* @return The kubeconfig
*/
protected KubeConfig createCubeConfig(String okeClusterId, Endpoint endpointType) {
LOG.info("Creating remote kubeconfig for cluster id {}", okeClusterId);
CreateClusterKubeconfigContentDetails body = CreateClusterKubeconfigContentDetails.builder()
.tokenVersion(TOKEN_VERSION)
.endpoint(endpointType)
.build();
CreateKubeconfigRequest kubeConfigRequest = CreateKubeconfigRequest.builder()
.clusterId(okeClusterId)
.createClusterKubeconfigContentDetails(body)
.build();

CreateKubeconfigResponse response;
try {
response = client.createKubeconfig(kubeConfigRequest);
} catch (Exception e) {
LOG.error("Caught exception when creating kubeconfig for cluster: {}", okeClusterId, e);
throw new IllegalStateException("Unable to create KubeConfig", e);
}
LOG.info("Successfully received kubeconfig response for cluster id {}", okeClusterId);
try (InputStream kubeConfig = response.getInputStream()) {
return loadKubeConfigFromInputStream(kubeConfig);
} catch (IOException e) {
LOG.error("Caught exception when reading kubeconfig for cluster {}", okeClusterId, e);
throw new RuntimeException("Unable to create kubeClient", e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.oraclecloud.oke.kubernetes.client;

import com.oracle.bmc.containerengine.model.CreateClusterKubeconfigContentDetails.Endpoint;
import io.micronaut.context.annotation.BootstrapContextCompatible;
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.bind.annotation.Bindable;
import io.micronaut.core.util.StringUtils;
import jakarta.validation.constraints.NotBlank;

/**
* Configuration for the OKE kubernetes client.
* If enabled, the client will retrieve a kubeconfig from the cluster and sign tokens
* with the OCI SDK authentication. This is not required on kubernetes nodes, as there
* kubeconfig should already be present.
*
* @param enabled Whether the client is enabled
* @param clusterId The OKE cluster ID
* @param endpointType Define which endpoint type to use. One of {@code PublicEndpoint},
* {@code PrivateEndpoint}, {@code VcnHostname} and {@code LegacyKubernetes}.
* Default is {@code PublicEndpoint}.
*
* @since 4.4.x
* @author Andriy Dmytruk
*/
@Requires(property = OkeKubernetesClientConfig.ENABLED, value = StringUtils.TRUE, defaultValue = StringUtils.TRUE)
@Requires(property = OkeKubernetesClientConfig.CLUSTER_ID)
@ConfigurationProperties(OkeKubernetesClientConfig.PREFIX)
@BootstrapContextCompatible
public record OkeKubernetesClientConfig(
@Bindable(defaultValue = StringUtils.TRUE)
boolean enabled,
@NonNull @NotBlank
String clusterId,
@Nullable @Bindable(defaultValue = "PublicEndpoint")
Endpoint endpointType
) {

public static final String PREFIX = "oci.oke.kubernetes.client";
public static final String ENABLED = PREFIX + ".enabled";
public static final String CLUSTER_ID = PREFIX + ".cluster-id";

}
Loading

0 comments on commit 6009a37

Please sign in to comment.