Skip to content
Snippets Groups Projects
Commit 84448fd3 authored by Sören Henning's avatar Sören Henning
Browse files

Add patcher for generic Kubernetes resources

parent 8bd80575
Branches
Tags
No related merge requests found
Pipeline #11123 failed
package rocks.theodolite.kubernetes.patcher
import com.fasterxml.jackson.annotation.JsonTypeName
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.node.JsonNodeType
import io.fabric8.kubernetes.api.model.GenericKubernetesResource
import io.fabric8.kubernetes.api.model.HasMetadata
import org.json.JSONString
/**
* Patches an arbitrary field in a [GenericKubernetesResource].
*
* @param path Path as List of Strings and Ints to the field to be patched.
*/
class GenericResourcePatcher(val path: List<Any>, val type: Type = Type.STRING) : AbstractStringPatcher() {
override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata {
if (resource is GenericKubernetesResource) {
val castedValue = when (type) {
Type.STRING -> value
Type.BOOLEAN -> value.toBoolean()
Type.NUMBER -> value.toDouble()
Type.INTEGER -> value.toInt()
}
var current: Any? = resource.additionalProperties
for (segment in path.dropLast(1)) {
current = if (segment is Int && current is MutableList<*> && current.size > segment) {
current.toTypedArray()[segment]
} else if (segment is String && current is Map<*, *>) {
current[segment]
} else {
throw IllegalArgumentException("Provided path is invalid")
}
}
val segment = path.lastOrNull()
if (segment == null) {
throw IllegalArgumentException("Path must not be empty")
} else if (segment is Int && current is MutableList<*> && current.size > segment) {
(current as MutableList<Any?>)[segment] = castedValue
} else if (segment is String && current is Map<*, *>) {
(current as MutableMap<String, Any>)[segment] = castedValue
} else {
throw IllegalArgumentException("Cannot set value for path")
}
}
return resource
}
enum class Type(val value: String) {
STRING("string"),
BOOLEAN("boolean"),
NUMBER("number"),
INTEGER("integer");
companion object {
fun from(type: String): Type =
values().find { it.value == type } ?: throw IllegalArgumentException("Requested Type does not exist")
}
}
}
...@@ -94,6 +94,12 @@ class PatcherFactory { ...@@ -94,6 +94,12 @@ class PatcherFactory {
"VolumesConfigMapPatcher" -> VolumesConfigMapPatcher( "VolumesConfigMapPatcher" -> VolumesConfigMapPatcher(
volumeName = patcher.properties["volumeName"] ?: throwInvalid(patcher) volumeName = patcher.properties["volumeName"] ?: throwInvalid(patcher)
) )
"GenericResourcePatcher" -> GenericResourcePatcher(
path = (patcher.properties["path"] ?: throwInvalid(patcher))
.split("/")
.map { it.toIntOrNull() ?: it },
type = patcher.properties["type"]?.let { GenericResourcePatcher.Type.from(it) } ?: GenericResourcePatcher.Type.STRING
)
else -> throw InvalidPatcherConfigurationException("Patcher type ${patcher.type} not found.") else -> throw InvalidPatcherConfigurationException("Patcher type ${patcher.type} not found.")
} }
} }
......
package rocks.theodolite.kubernetes.patcher
import io.fabric8.kubernetes.api.model.GenericKubernetesResource
import io.fabric8.kubernetes.api.model.HasMetadata
import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext
import io.fabric8.kubernetes.client.server.mock.KubernetesServer
import io.quarkus.test.junit.QuarkusTest
import io.quarkus.test.kubernetes.client.KubernetesTestServer
import io.quarkus.test.kubernetes.client.WithKubernetesTestServer
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import registerResource
@QuarkusTest
@WithKubernetesTestServer
internal class GenericResourcePatcherTest {
@KubernetesTestServer
private lateinit var server: KubernetesServer
@Test
fun testPatchString() {
val sourceResource = listOf(createServiceMonitor())
val patcher = GenericResourcePatcher(listOf("spec", "endpoints", 0, "interval"))
val patchedResource = patcher.patch(sourceResource, "20s")
patchedResource.forEach {
assertEquals(
"20s",
((((it as GenericKubernetesResource)
.additionalProperties["spec"] as Map<String, Any>)
["endpoints"] as List<Any>)
[0] as Map<String, Any>)
["interval"])
}
}
@Test
fun testPatchBoolean() {
val sourceResource = listOf(createServiceMonitor())
val patcher = GenericResourcePatcher(listOf("spec", "endpoints", 0, "honorTimestamps"), GenericResourcePatcher.Type.BOOLEAN)
val patchedResource = patcher.patch(sourceResource, "true")
patchedResource.forEach {
assertEquals(
true,
((((it as GenericKubernetesResource)
.additionalProperties["spec"] as Map<String, Any>)
["endpoints"] as List<Any>)
[0] as Map<String, Any>)
["honorTimestamps"])
}
}
@Test
fun testPatchInteger() {
val sourceResource = listOf(createServiceMonitor())
val patcher = GenericResourcePatcher(listOf("spec", "labelLimit"), GenericResourcePatcher.Type.INTEGER)
val patchedResource = patcher.patch(sourceResource, "11")
patchedResource.forEach {
assertEquals(
11,
((it as GenericKubernetesResource)
.additionalProperties["spec"] as Map<String, Any>)
["labelLimit"])
}
}
@Test
fun testPatchNumber() {
val sourceResource = listOf(createServiceMonitor())
val patcher = GenericResourcePatcher(listOf("spec", "myMadeUpProp"), GenericResourcePatcher.Type.NUMBER)
val patchedResource = patcher.patch(sourceResource, "11.2")
patchedResource.forEach {
assertEquals(
11.2,
((it as GenericKubernetesResource)
.additionalProperties["spec"] as Map<String, Any>)
["labelLimit"])
}
}
@Test
fun testPatchNumberWithoutDecimals() {
val sourceResource = listOf(createServiceMonitor())
val patcher = GenericResourcePatcher(listOf("spec", "myMadeUpProp"), GenericResourcePatcher.Type.NUMBER)
val patchedResource = patcher.patch(sourceResource, "11")
patchedResource.forEach {
assertEquals(
11.0,
((it as GenericKubernetesResource)
.additionalProperties["spec"] as Map<String, Any>)
["labelLimit"])
}
}
fun createServiceMonitor(): HasMetadata {
val serviceMonitorContext = ResourceDefinitionContext.Builder()
.withGroup("monitoring.coreos.com")
.withKind("ServiceMonitor")
.withPlural("servicemonitors")
.withNamespaced(true)
.withVersion("v1")
.build()
server.registerResource(serviceMonitorContext)
val serviceMonitorStream = javaClass.getResourceAsStream("/k8s-resource-files/test-service-monitor.yaml")
return server.client.load(serviceMonitorStream).get()[0]
}
}
package rocks.theodolite.kubernetes.patcher
import io.quarkus.test.junit.QuarkusTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
@QuarkusTest
internal class PatcherFactoryTest {
@Test
fun testGenericResourcePatcherWithoutType() {
val patcherDefinition = PatcherDefinition()
patcherDefinition.type = "GenericResourcePatcher"
patcherDefinition.properties = mapOf(
"path" to "some/path/123/toSomeField"
)
val patcher = PatcherFactory.createPatcher(patcherDefinition)
assertTrue(patcher is GenericResourcePatcher)
val castedPatcher = patcher as GenericResourcePatcher
assertEquals(listOf("some", "path", 123, "toSomeField"), castedPatcher.path)
assertEquals(GenericResourcePatcher.Type.STRING, castedPatcher.type)
}
@Test
fun testGenericResourcePatcherWithStringType() {
val patcherDefinition = PatcherDefinition()
patcherDefinition.type = "GenericResourcePatcher"
patcherDefinition.properties = mapOf(
"path" to "spec",
"type" to "string"
)
val patcher = PatcherFactory.createPatcher(patcherDefinition)
assertTrue(patcher is GenericResourcePatcher)
val castedPatcher = patcher as GenericResourcePatcher
assertEquals(GenericResourcePatcher.Type.STRING, castedPatcher.type)
}
@Test
fun testGenericResourcePatcherWithBooleanType() {
val patcherDefinition = PatcherDefinition()
patcherDefinition.type = "GenericResourcePatcher"
patcherDefinition.properties = mapOf(
"path" to "spec",
"type" to "boolean"
)
val patcher = PatcherFactory.createPatcher(patcherDefinition)
assertTrue(patcher is GenericResourcePatcher)
val castedPatcher = patcher as GenericResourcePatcher
assertEquals(GenericResourcePatcher.Type.BOOLEAN, castedPatcher.type)
}
@Test
fun testGenericResourcePatcherWithIntegerType() {
val patcherDefinition = PatcherDefinition()
patcherDefinition.type = "GenericResourcePatcher"
patcherDefinition.properties = mapOf(
"path" to "spec",
"type" to "integer"
)
val patcher = PatcherFactory.createPatcher(patcherDefinition)
assertTrue(patcher is GenericResourcePatcher)
val castedPatcher = patcher as GenericResourcePatcher
assertEquals(GenericResourcePatcher.Type.INTEGER, castedPatcher.type)
}
@Test
fun testGenericResourcePatcherWithNumberType() {
val patcherDefinition = PatcherDefinition()
patcherDefinition.type = "GenericResourcePatcher"
patcherDefinition.properties = mapOf(
"path" to "spec",
"type" to "number"
)
val patcher = PatcherFactory.createPatcher(patcherDefinition)
assertTrue(patcher is GenericResourcePatcher)
val castedPatcher = patcher as GenericResourcePatcher
assertEquals(GenericResourcePatcher.Type.NUMBER, castedPatcher.type)
}
}
\ No newline at end of file
...@@ -5,3 +5,10 @@ metadata: ...@@ -5,3 +5,10 @@ metadata:
app: titan-ccp-aggregation app: titan-ccp-aggregation
appScope: titan-ccp appScope: titan-ccp
name: test-service-monitor name: test-service-monitor
spec:
selector:
matchLabels:
app: flink
endpoints:
- port: metrics
interval: 10s
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment