Skip to content

Commit

Permalink
save kube conf files when current ctx or ns changes
Browse files Browse the repository at this point in the history
Signed-off-by: Andre Dietisheim <[email protected]>
  • Loading branch information
adietish committed Sep 11, 2024
1 parent 4e06c8c commit 9cce0e9
Show file tree
Hide file tree
Showing 9 changed files with 501 additions and 401 deletions.
11 changes: 11 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ sourceSets {
compileClasspath += sourceSets.main.output + configurations.runtimeClasspath
runtimeClasspath += output + compileClasspath
}
main {
java.srcDirs("src/main/java")
kotlin.srcDirs("src/main/kotlin")
}
test {
java.srcDirs("src/test/java")
kotlin.srcDirs("src/test/kotlin")
// #779: unit tests need to see kubernetes-client classes in src/main/java
compileClasspath += sourceSets.main.output + configurations.runtimeClasspath
runtimeClasspath += output + compileClasspath
}
}

task integrationTest(type: Test) {
Expand Down
23 changes: 20 additions & 3 deletions src/main/java/io/fabric8/kubernetes/client/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -1789,13 +1789,30 @@ public File getFile() {
}

public KubeConfigFile getFileWithAuthInfo(String name) {
if (Utils.isNullOrEmpty(name)
|| Utils.isNullOrEmpty(getFiles())) {
if (Utils.isNullOrEmpty(name)) {
return null;
}
return getFirstKubeConfigFileMatching(config -> KubeConfigUtils.hasAuthInfoNamed(config, name));
}

public KubeConfigFile getFileWithContext(String name) {
if (Utils.isNullOrEmpty(name)) {
return null;
}
return getFirstKubeConfigFileMatching(config -> KubeConfigUtils.getContext(config, name) != null);
}

public KubeConfigFile getFileWithCurrentContext() {
return getFirstKubeConfigFileMatching(config -> Utils.isNotNullOrEmpty(config.getCurrentContext()));
}

private KubeConfigFile getFirstKubeConfigFileMatching(Predicate<io.fabric8.kubernetes.api.model.Config> predicate) {
if (Utils.isNullOrEmpty(kubeConfigFiles)) {
return null;
}
return kubeConfigFiles.stream()
.filter(KubeConfigFile::isReadable)
.filter(entry -> KubeConfigUtils.hasAuthInfoNamed(entry.getConfig(), name))
.filter(entry -> predicate.test(entry.getConfig()))
.findFirst()
.orElse(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,31 @@ public static Config parseConfigFromString(String contents) {
public static NamedContext getCurrentContext(Config config) {
String contextName = config.getCurrentContext();
if (contextName != null) {
return getContext(config, contextName);
}
return null;
}

/**
* Returns the {@link NamedContext} with the given name.
* Returns {@code null} otherwise
*
* @param config the config to search
* @param name the context name to match
* @return the context with the the given name
*/
public static NamedContext getContext(Config config, String name) {
NamedContext context = null;
if (config != null && name != null) {
List<NamedContext> contexts = config.getContexts();
if (contexts != null) {
for (NamedContext context : contexts) {
if (contextName.equals(context.getName())) {
return context;
}
}
context = contexts.stream()
.filter(toInspect -> name.equals(toInspect.getName()))
.findAny()
.orElse(null);
}
}
return null;
return context;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ package com.redhat.devtools.intellij.kubernetes.model

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger
import com.redhat.devtools.intellij.common.utils.ConfigHelper
import com.redhat.devtools.intellij.common.utils.ConfigWatcher
import com.redhat.devtools.intellij.common.utils.ExecHelper
import com.redhat.devtools.intellij.kubernetes.model.client.ClientAdapter
Expand Down Expand Up @@ -249,7 +248,7 @@ open class AllContexts(
* [com.redhat.devtools.intellij.kubernetes.model.client.ClientConfig].
* Closing/Recreating [ConfigWatcher] is needed when used within [com.redhat.devtools.intellij.kubernetes.model.client.ClientConfig].
* The latter gets closed/recreated whenever the context changes in
* [com.redhat.devtools.intellij.kubernetes.model.client.KubeConfigAdapter].
* [com.redhat.devtools.intellij.kubernetes.model.client.KubeConfigPersistence].
*/
val watcher = ConfigWatcher(Paths.get(filename)) { _, config: io.fabric8.kubernetes.api.model.Config? -> onKubeConfigChanged(config) }
runAsync(watcher::run)
Expand All @@ -259,8 +258,7 @@ open class AllContexts(
lock.read {
fileConfig ?: return
val client = client.get() ?: return
val clientConfig = client.config.configuration
if (ConfigHelper.areEqual(fileConfig, clientConfig)) {
if (client.config.isEqual(fileConfig)) {
return
}
this.client.reset() // create new client when accessed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,31 @@
******************************************************************************/
package com.redhat.devtools.intellij.kubernetes.model.client

import com.intellij.openapi.diagnostic.logger
import com.redhat.devtools.intellij.common.utils.ConfigHelper
import com.redhat.devtools.intellij.kubernetes.CompletableFutureUtils.PLATFORM_EXECUTOR
import io.fabric8.kubernetes.api.model.Context
import io.fabric8.kubernetes.api.model.NamedContext
import io.fabric8.kubernetes.client.Client
import io.fabric8.kubernetes.client.Config
import io.fabric8.kubernetes.client.internal.KubeConfigUtils
import java.io.File
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor

/**
* An adapter to access [io.fabric8.kubernetes.client.Config].
* It also saves the kube config [KubeConfigUtils] when it changes the client config.
*/
open class ClientConfig(private val client: Client, private val executor: Executor = PLATFORM_EXECUTOR) {
open class ClientConfig(
private val client: Client,
private val executor: Executor = PLATFORM_EXECUTOR,
private val persistence: (io.fabric8.kubernetes.api.model.Config?, String?) -> Unit = KubeConfigUtils::persistKubeConfigIntoFile
) {

open var currentContext: NamedContext?
open val currentContext: NamedContext?
get() {
return configuration.currentContext
}
set(context) {
configuration.currentContext = context
}

open val allContexts: List<NamedContext>
get() {
Expand All @@ -43,73 +45,57 @@ open class ClientConfig(private val client: Client, private val executor: Execut
client.configuration
}

protected open val kubeConfig: KubeConfigAdapter by lazy {
KubeConfigAdapter()
}

fun save(): CompletableFuture<Boolean> {
return CompletableFuture.supplyAsync(
{
if (!kubeConfig.exists()) {
return@supplyAsync false
val toSave = mutableMapOf<File, io.fabric8.kubernetes.api.model.Config>()
val withCurrentContext = configuration.fileWithCurrentContext
if (withCurrentContext != null
&& setCurrentContext(withCurrentContext.config)
) {
toSave[withCurrentContext.file] = withCurrentContext.config
}
val fromFile = kubeConfig.load() ?: return@supplyAsync false
if (setCurrentContext(
currentContext,
KubeConfigUtils.getCurrentContext(fromFile),
fromFile
).or( // no short-circuit
setCurrentNamespace(
currentContext?.context,
KubeConfigUtils.getCurrentContext(fromFile)?.context
)
)
val withCurrentNamespace = configuration.getFileWithContext(currentContext?.name)
if (withCurrentNamespace != null
&& setCurrentNamespace(withCurrentNamespace.config)
) {
kubeConfig.save(fromFile)
return@supplyAsync true
} else {
return@supplyAsync false
toSave[withCurrentNamespace.file] = withCurrentNamespace.config
}
toSave.forEach {
save(it.value, it.key)
}
toSave.isNotEmpty()
},
executor
)
}

private fun setCurrentContext(
currentContext: NamedContext?,
kubeConfigCurrentContext: NamedContext?,
kubeConfig: io.fabric8.kubernetes.api.model.Config
): Boolean {
return if (currentContext != null
&& !ConfigHelper.areEqual(currentContext, kubeConfigCurrentContext)
) {
kubeConfig.currentContext = currentContext.name
private fun save(kubeConfig: io.fabric8.kubernetes.api.model.Config?, file: File?) {
if (kubeConfig != null
&& file?.absolutePath != null) {
logger<ClientConfig>().debug("Saving ${file.absolutePath}.")
persistence.invoke(kubeConfig, file.absolutePath)
}
}

private fun setCurrentNamespace(kubeConfig: io.fabric8.kubernetes.api.model.Config?): Boolean {
val currentNamespace = currentContext?.context?.namespace ?: return false
val context = KubeConfigUtils.getContext(kubeConfig, currentContext?.name)
return if (context?.context != null
&& context.context.namespace != currentNamespace) {
context.context.namespace = currentNamespace
true
} else {
false
}
}

/**
* Sets the namespace in the given source [Context] to the given target [Context].
* Does nothing if the target config has no current context
* or if the source config has no current context
* or if setting it would not change it.
*
* @param source Context whose namespace should be copied
* @param target Context whose namespace should be overriden
* @return
*/
private fun setCurrentNamespace(
source: Context?,
target: Context?
): Boolean {
val sourceNamespace = source?.namespace ?: return false
val targetNamespace = target?.namespace
return if (target != null
&& sourceNamespace != targetNamespace
) {
target.namespace = source.namespace
private fun setCurrentContext(kubeConfig: io.fabric8.kubernetes.api.model.Config?): Boolean {
val currentContext = currentContext?.name ?: return false
return if (
kubeConfig != null
&& currentContext != kubeConfig.currentContext) {
kubeConfig.currentContext = currentContext
true
} else {
false
Expand All @@ -119,4 +105,8 @@ open class ClientConfig(private val client: Client, private val executor: Execut
fun isCurrent(context: NamedContext): Boolean {
return context == currentContext
}

fun isEqual(config: io.fabric8.kubernetes.api.model.Config): Boolean {
return ConfigHelper.areEqual(config, configuration)
}
}

This file was deleted.

Loading

0 comments on commit 9cce0e9

Please sign in to comment.