Skip to content

Commit 583271d

Browse files
committed
feat(extensions): add support for local Kerberos material copy paths
- Introduced `localClientKeytabCopyPaths` and `localKrb5ConfCopyPaths` to allow copying Kerberos client keytab and krb5.conf to multiple local paths. - Extended `BigDataExtensionsConfigLoader` and `BigDataExtensionsBuilder` to configure new paths via TOML or programmatic APIs. - Updated `KerberosMaterialExtension` to handle file copying with path validation and overwrite support. - Added documentation and examples for configuring new paths. - Included unit test to validate loading of copy paths from TOML configurations.
1 parent 31dceee commit 583271d

5 files changed

Lines changed: 137 additions & 2 deletions

File tree

doc/user-guide.adoc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,29 @@ The `KerberosMaterialExtension` publishes reusable client and service material t
12201220
----
12211221
[kerberosMaterial]
12221222
enabled = true
1223+
localClientKeytabCopyPaths = [
1224+
"build/app/client.keytab",
1225+
"build/worker/client.keytab"
1226+
]
1227+
localKrb5ConfCopyPaths = [
1228+
"build/app/krb5.conf",
1229+
"build/worker/krb5.conf"
1230+
]
1231+
----
1232+
1233+
`localClientKeytabCopyPaths` copies the same generated client keytab to multiple stable host-local paths. This is useful when several app frameworks, tools, or IDE run configurations expect their own file location. `localKrb5ConfCopyPaths` does the same for the generated client `krb5.conf`. Existing files at those destinations are overwritten.
1234+
1235+
Programmatic configuration is also supported:
1236+
1237+
[source,kotlin]
1238+
----
1239+
bigDataExtensions {
1240+
kerberosMaterial {
1241+
localClientKeytabCopyPath("build/app/client.keytab")
1242+
localClientKeytabCopyPath("build/worker/client.keytab")
1243+
localKrb5ConfCopyPath("build/app/krb5.conf")
1244+
}
1245+
}
12231246
----
12241247

12251248
Common output keys are:
@@ -1230,6 +1253,10 @@ Common output keys are:
12301253
* `kerberos-material.client.principal`
12311254
* `kerberos-material.client.password`
12321255
* `kerberos-material.client.keytab`
1256+
* `kerberos-material.client.keytab.copies`
1257+
* `kerberos-material.client.keytab.copy.0`
1258+
* `kerberos-material.krb5-conf.copies`
1259+
* `kerberos-material.krb5-conf.copy.0`
12331260
* `kerberos-material.kafka.service-name`
12341261
* `kerberos-material.kafka.principal`
12351262
* `kerberos-material.kafka.keytab`

extensions/src/main/kotlin/org/openprojectx/bigdata/test/extensions/config/BigDataExtensionsBuilder.kt

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,21 @@ class BigDataExtensionsBuilder {
4444
}
4545

4646
fun kerberosMaterial(id: String = "kerberos-material") {
47-
extensions += KerberosMaterialExtension(id = id)
47+
extensions += KerberosMaterialBuilder().apply { this.id = id }.build()
4848
}
4949

5050
fun kerberosMaterial() {
5151
kerberosMaterial(id = "kerberos-material")
5252
}
5353

54+
fun kerberosMaterial(configure: KerberosMaterialBuilder.() -> Unit) {
55+
extensions += KerberosMaterialBuilder().apply(configure).build()
56+
}
57+
58+
fun kerberosMaterial(configure: Consumer<KerberosMaterialBuilder>) {
59+
extensions += KerberosMaterialBuilder().also { configure.accept(it) }.build()
60+
}
61+
5462
fun s3Bucket(bucket: String, id: String = "s3-bucket-$bucket") {
5563
extensions += S3BucketExtension(id = id, bucket = bucket)
5664
}
@@ -185,6 +193,35 @@ class S3JceksBuilder {
185193
)
186194
}
187195

196+
class KerberosMaterialBuilder {
197+
var id: String = "kerberos-material"
198+
private val localClientKeytabCopyPaths = mutableListOf<String>()
199+
private val localKrb5ConfCopyPaths = mutableListOf<String>()
200+
201+
fun localClientKeytabCopyPath(path: String) {
202+
localClientKeytabCopyPaths += path
203+
}
204+
205+
fun localClientKeytabCopyPaths(paths: Iterable<String>) {
206+
localClientKeytabCopyPaths += paths
207+
}
208+
209+
fun localKrb5ConfCopyPath(path: String) {
210+
localKrb5ConfCopyPaths += path
211+
}
212+
213+
fun localKrb5ConfCopyPaths(paths: Iterable<String>) {
214+
localKrb5ConfCopyPaths += paths
215+
}
216+
217+
internal fun build(): KerberosMaterialExtension =
218+
KerberosMaterialExtension(
219+
id = id,
220+
localClientKeytabCopyPaths = localClientKeytabCopyPaths.toList(),
221+
localKrb5ConfCopyPaths = localKrb5ConfCopyPaths.toList(),
222+
)
223+
}
224+
188225
class KafkaAvroBuilder {
189226
var id: String = "kafka-avro-seed"
190227
private val topics = mutableListOf<KafkaAvroTopicSeed>()

extensions/src/main/kotlin/org/openprojectx/bigdata/test/extensions/config/BigDataExtensionsConfigLoader.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ class BigDataExtensionsConfigLoader(
7171
if (config.boolean("enabled", default = true)) {
7272
extensions += KerberosMaterialExtension(
7373
id = config.string("id", "kerberos-material"),
74+
localClientKeytabCopyPaths = config.stringList("localClientKeytabCopyPaths"),
75+
localKrb5ConfCopyPaths = config.stringList("localKrb5ConfCopyPaths"),
7476
)
7577
}
7678
}
@@ -167,6 +169,12 @@ class BigDataExtensionsConfigLoader(
167169
private fun JsonObject.stringMap(name: String): Map<String, String> =
168170
this[name]?.jsonObject?.flattenStringMap(errorPath = name).orEmpty()
169171

172+
private fun JsonObject.stringList(name: String): List<String> =
173+
this[name]?.jsonArray?.mapIndexed { index, item ->
174+
item.jsonPrimitive.contentOrNull
175+
?: error("Extension config field '$name[$index]' must be a string")
176+
}.orEmpty()
177+
170178
private fun JsonObject.flattenStringMap(prefix: String = "", errorPath: String): Map<String, String> =
171179
flatMap { (key, value) ->
172180
val outputKey = if (prefix.isBlank()) key else "$prefix.$key"

extensions/src/main/kotlin/org/openprojectx/bigdata/test/extensions/kerberos/KerberosMaterialExtension.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ import org.openprojectx.bigdata.test.core.BigDataService
44
import org.openprojectx.bigdata.test.extensions.core.BigDataExtension
55
import org.openprojectx.bigdata.test.extensions.core.BigDataExtensionContext
66
import org.openprojectx.bigdata.test.extensions.core.BigDataExtensionEvent
7+
import java.nio.file.Files
8+
import java.nio.file.Path
9+
import java.nio.file.StandardCopyOption
710

811
data class KerberosMaterialExtension(
912
override val id: String = "kerberos-material",
13+
val localClientKeytabCopyPaths: List<String> = emptyList(),
14+
val localKrb5ConfCopyPaths: List<String> = emptyList(),
1015
) : BigDataExtension {
1116
override val requiredServices: Set<BigDataService> = setOf(BigDataService.KERBEROS)
1217
override val events: Set<BigDataExtensionEvent> = setOf(BigDataExtensionEvent.AFTER_KIT_START)
@@ -18,8 +23,12 @@ data class KerberosMaterialExtension(
1823
put(context, "krb5-conf", kerberos.property("bigdata.test.kerberos.krb5-conf"))
1924
put(context, "client.principal", kerberos.property("bigdata.test.kerberos.client-principal"))
2025
put(context, "client.password", kerberos.property("bigdata.test.kerberos.client-password"))
21-
put(context, "client.keytab", kerberos.property("bigdata.test.kerberos.client-keytab"))
26+
val clientKeytab = kerberos.property("bigdata.test.kerberos.client-keytab")
27+
val krb5Conf = kerberos.property("bigdata.test.kerberos.krb5-conf")
28+
put(context, "client.keytab", clientKeytab)
2229
put(context, "client.keytab.container", kerberos.property("bigdata.test.kerberos.client-keytab.container"))
30+
copyMaterial(context, "client.keytab", clientKeytab, localClientKeytabCopyPaths)
31+
copyMaterial(context, "krb5-conf", krb5Conf, localKrb5ConfCopyPaths)
2332

2433
context.kit.endpoints().forEach { (service, endpoint) ->
2534
endpoint.properties.forEach { (key, value) ->
@@ -35,6 +44,24 @@ data class KerberosMaterialExtension(
3544
context.putOutput("$id.$key", value)
3645
}
3746

47+
private fun copyMaterial(
48+
context: BigDataExtensionContext,
49+
outputKey: String,
50+
source: String,
51+
destinations: List<String>,
52+
) {
53+
if (destinations.isEmpty()) return
54+
55+
val sourcePath = Path.of(source)
56+
destinations.forEachIndexed { index, destination ->
57+
val destinationPath = Path.of(destination)
58+
destinationPath.parent?.let { Files.createDirectories(it) }
59+
Files.copy(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING)
60+
put(context, "$outputKey.copy.$index", destinationPath.toString())
61+
}
62+
put(context, "$outputKey.copies", destinations.joinToString(","))
63+
}
64+
3865
private val BigDataService.key: String
3966
get() = name.lowercase().replace('_', '-')
4067
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.openprojectx.bigdata.test.extensions.kerberos
2+
3+
import org.junit.jupiter.api.Assertions.assertEquals
4+
import org.junit.jupiter.api.Test
5+
import org.openprojectx.bigdata.test.extensions.config.BigDataExtensionsConfigLoader
6+
7+
class KerberosMaterialExtensionTest {
8+
@Test
9+
fun `loads local kerberos material copy paths from toml`() {
10+
val extension = BigDataExtensionsConfigLoader().load(
11+
"""
12+
[kerberosMaterial]
13+
enabled = true
14+
id = "kerberos-copy"
15+
localClientKeytabCopyPaths = [
16+
"build/app/client.keytab",
17+
"build/worker/client.keytab"
18+
]
19+
localKrb5ConfCopyPaths = [
20+
"build/app/krb5.conf",
21+
"build/worker/krb5.conf"
22+
]
23+
""".trimIndent(),
24+
).single() as KerberosMaterialExtension
25+
26+
assertEquals("kerberos-copy", extension.id)
27+
assertEquals(
28+
listOf("build/app/client.keytab", "build/worker/client.keytab"),
29+
extension.localClientKeytabCopyPaths,
30+
)
31+
assertEquals(
32+
listOf("build/app/krb5.conf", "build/worker/krb5.conf"),
33+
extension.localKrb5ConfCopyPaths,
34+
)
35+
}
36+
}

0 commit comments

Comments
 (0)