Skip to content

Commit ed944d5

Browse files
committed
Fix kubernetes client config
- We always need a client, and the default config should work even outside a cluster - The conditional property was inverted anyway - Since we now always have a client, make it non-nullable in the service - For local dev/run, add a 'localKubernetesContext' to control which context is used instead of the current context by default Pass '-PjvmArgs=-DlocalKubernetesContext=my-context' to the gradle bootRun task
1 parent 8f15898 commit ed944d5

2 files changed

Lines changed: 28 additions & 20 deletions

File tree

src/main/kotlin/com/cosmotech/api/utils/KubernetesApiConfig.kt

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,30 @@ package com.cosmotech.api.utils
55
import com.cosmotech.api.config.CsmPlatformProperties
66
import io.kubernetes.client.openapi.apis.CoreV1Api
77
import io.kubernetes.client.util.ClientBuilder
8-
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
8+
import io.kubernetes.client.util.KubeConfig
9+
import java.io.FileReader
10+
import java.io.IOException
911
import org.springframework.context.annotation.Bean
1012
import org.springframework.context.annotation.Configuration
1113

1214
@Configuration
1315
open class KubernetesApiConfig {
1416
@Bean
15-
@ConditionalOnProperty(
16-
name = ["csm.platform.runOutsideKubernetes"], havingValue = "false", matchIfMissing = true)
17-
open fun coreV1Api(csmPlatformProperties: CsmPlatformProperties): CoreV1Api? {
18-
val client = ClientBuilder.defaultClient()
19-
return CoreV1Api(client)
17+
open fun coreV1Api(csmPlatformProperties: CsmPlatformProperties): CoreV1Api {
18+
val kubernetesContext = System.getProperty("localKubernetesContext")
19+
if (kubernetesContext != null) {
20+
// Locate kube config file
21+
val kubeConfigPath =
22+
System.getenv("KUBECONFIG") ?: System.getenv("HOME")?.plus("/.kube/config")
23+
kubeConfigPath ?: throw IOException("Unable to locate a kube config file")
24+
25+
// Load config and force the context
26+
var kubeConfig = KubeConfig.loadKubeConfig(FileReader(kubeConfigPath))
27+
kubeConfig.setContext(kubernetesContext)
28+
29+
return CoreV1Api(ClientBuilder.kubeconfig(kubeConfig).build())
30+
} else {
31+
return CoreV1Api(ClientBuilder.defaultClient())
32+
}
2033
}
2134
}

src/main/kotlin/com/cosmotech/api/utils/KubernetesService.kt

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import org.springframework.stereotype.Service
1212
private const val SECRET_LABEL = "cosmotech.com/context"
1313

1414
@Service
15-
class KubernetesService(private val kubernetesApi: CoreV1Api?) : SecretManager {
15+
class KubernetesService(private val kubernetesApi: CoreV1Api) : SecretManager {
1616

1717
private val logger = LoggerFactory.getLogger(KubernetesService::class.java)
1818

@@ -30,23 +30,22 @@ class KubernetesService(private val kubernetesApi: CoreV1Api?) : SecretManager {
3030
}
3131

3232
private fun deleteSecretFromKubernetes(namespace: String, secretName: String) {
33-
val api = checkKubernetesContext()
3433
val secretNameLower = secretName.lowercase()
3534
val labelSelector = buildLabelSector(secretNameLower)
3635

37-
val secrets = api.listNamespacedSecret(namespace).labelSelector(labelSelector).execute()
36+
val secrets =
37+
kubernetesApi.listNamespacedSecret(namespace).labelSelector(labelSelector).execute()
3838
if (secrets.items.isEmpty()) {
3939
logger.debug("Secret does not exists in namespace $namespace: cannot delete it")
4040
} else {
4141
logger.info("Secret exists in namespace $namespace: deleting it")
42-
api.deleteNamespacedSecret(secretNameLower, namespace).execute()
42+
kubernetesApi.deleteNamespacedSecret(secretNameLower, namespace).execute()
4343
}
4444
}
4545

4646
private fun getSecretFromKubernetes(namespace: String, secretName: String): Map<String, String> {
47-
val api = checkKubernetesContext()
4847
val secretNameLower = secretName.lowercase()
49-
val result = api.readNamespacedSecret(secretNameLower, namespace).execute()
48+
val result = kubernetesApi.readNamespacedSecret(secretNameLower, namespace).execute()
5049

5150
logger.debug("Secret retrieved for namespace $namespace")
5251
return result.data?.mapValues { Base64.getDecoder().decode(it.value).toString(Charsets.UTF_8) }
@@ -58,7 +57,6 @@ class KubernetesService(private val kubernetesApi: CoreV1Api?) : SecretManager {
5857
secretName: String,
5958
secretData: Map<String, String>
6059
) {
61-
val api = checkKubernetesContext()
6260
logger.debug("Creating secret $secretName in namespace $namespace")
6361

6462
val secretNameLower = secretName.lowercase()
@@ -74,20 +72,17 @@ class KubernetesService(private val kubernetesApi: CoreV1Api?) : SecretManager {
7472
body.data = secretData.mapValues { Base64.getEncoder().encode(it.value.toByteArray()) }
7573
body.type = "Opaque"
7674

77-
val secrets = api.listNamespacedSecret(namespace).labelSelector(labelSelector).execute()
75+
val secrets =
76+
kubernetesApi.listNamespacedSecret(namespace).labelSelector(labelSelector).execute()
7877
if (secrets.items.isEmpty()) {
7978
logger.debug("Secret does not exists in namespace $namespace: creating it")
80-
api.createNamespacedSecret(namespace, body).execute()
79+
kubernetesApi.createNamespacedSecret(namespace, body).execute()
8180
} else {
8281
logger.debug("Secret already exists in namespace $namespace: replacing it")
83-
api.replaceNamespacedSecret(secretNameLower, namespace, body).execute()
82+
kubernetesApi.replaceNamespacedSecret(secretNameLower, namespace, body).execute()
8483
}
8584
logger.info("Secret created/replaced")
8685
}
8786

88-
private fun checkKubernetesContext(): CoreV1Api {
89-
return this.kubernetesApi ?: throw IllegalStateException("Kubernetes API is not available")
90-
}
91-
9287
private fun buildLabelSector(secretName: String) = "$SECRET_LABEL=$secretName"
9388
}

0 commit comments

Comments
 (0)