diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml
index 5b09cf1037..6a86b16f69 100644
--- a/.github/workflows/e2e-test.yml
+++ b/.github/workflows/e2e-test.yml
@@ -25,6 +25,7 @@ jobs:
- "sample-operators/webpage"
- "sample-operators/leader-election"
- "sample-operators/operations"
+ - "sample-operators/kotlin-operator"
runs-on: ubuntu-latest
steps:
- name: Checkout
diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml
index 5763c5490a..c2463fcee2 100644
--- a/operator-framework-core/pom.xml
+++ b/operator-framework-core/pom.xml
@@ -30,6 +30,11 @@
Operator SDK - Framework - Core
Core framework for implementing Kubernetes operators
+
+
+ 2.4.10
+
+
io.github.java-diff-utils
@@ -101,6 +106,13 @@
kube-api-test-client-inject
test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${kotlin.version}
+ test
+
@@ -147,6 +159,37 @@
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+ ${java.version}
+
+
+
+ kotlin-test-compile
+
+ test-compile
+
+ process-test-sources
+
+
+ ${project.basedir}/src/test/kotlin
+
+ ${project.basedir}/src/test/java
+
+
+
+
+
diff --git a/operator-framework-core/src/test/kotlin/io/javaoperatorsdk/operator/processing/dependent/workflow/KotlinCheckedExceptionDependentResourceTest.kt b/operator-framework-core/src/test/kotlin/io/javaoperatorsdk/operator/processing/dependent/workflow/KotlinCheckedExceptionDependentResourceTest.kt
new file mode 100644
index 0000000000..20198540be
--- /dev/null
+++ b/operator-framework-core/src/test/kotlin/io/javaoperatorsdk/operator/processing/dependent/workflow/KotlinCheckedExceptionDependentResourceTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright Java Operator SDK 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
+ *
+ * http://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.javaoperatorsdk.operator.processing.dependent.workflow
+
+import io.javaoperatorsdk.operator.AggregatedOperatorException
+import io.javaoperatorsdk.operator.api.reconciler.Context
+import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource
+import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext
+import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever
+import io.javaoperatorsdk.operator.sample.simple.TestCustomResource
+import java.util.concurrent.Executors
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+/**
+ * Smoke test verifying that JOSDK works properly when used from Kotlin.
+ *
+ * Unlike Java, Kotlin does not distinguish between checked and unchecked exceptions, so a Kotlin
+ * [DependentResource] can throw a plain [java.lang.Exception] from `reconcile` without declaring
+ * it, even though the Java `DependentResource.reconcile` method does not declare `throws
+ * Exception`. Before https://github.com/operator-framework/java-operator-sdk/pull/2965 such an
+ * exception was not caught by [NodeExecutor], since it only handled [RuntimeException], so it
+ * would not have been recorded as an error and retries would not have been triggered. This test
+ * makes sure such an exception is properly caught and reported, see
+ * https://github.com/operator-framework/java-operator-sdk/issues/2967.
+ */
+class KotlinCheckedExceptionDependentResourceTest {
+
+ private class CheckedException(message: String) : Exception(message)
+
+ private class ThrowingDependentResource :
+ DependentResource {
+ override fun reconcile(
+ primary: TestCustomResource,
+ context: Context
+ ): ReconcileResult {
+ throw CheckedException("checked exception thrown from Kotlin")
+ }
+
+ override fun resourceType(): Class = String::class.java
+ }
+
+ @Test
+ fun checkedExceptionThrownFromKotlinDependentResourceTriggersRetry() {
+ val dependentResource = ThrowingDependentResource()
+
+ val workflow =
+ WorkflowBuilder()
+ .addDependentResource(dependentResource)
+ .withThrowExceptionFurther(false)
+ .build()
+
+ @Suppress("UNCHECKED_CAST")
+ val context = mock(Context::class.java) as Context
+ val executorService = Executors.newSingleThreadExecutor()
+ try {
+ `when`(context.managedWorkflowAndDependentResourceContext())
+ .thenReturn(mock(ManagedWorkflowAndDependentResourceContext::class.java))
+ `when`(context.workflowExecutorService).thenReturn(executorService)
+ @Suppress("UNCHECKED_CAST")
+ `when`(context.eventSourceRetriever())
+ .thenReturn(mock(EventSourceRetriever::class.java) as EventSourceRetriever)
+
+ val result = workflow.reconcile(TestCustomResource(), context)
+
+ // the checked exception was caught by the workflow executor, not left uncaught, so it is
+ // reported as an error for the dependent resource, which is what allows JOSDK to retry the
+ // reconciliation.
+ assertThat(result.erroredDependents).containsOnlyKeys(dependentResource)
+ assertThat(result.erroredDependents[dependentResource])
+ .isInstanceOf(CheckedException::class.java)
+ assertThrows { result.throwAggregateExceptionIfErrorsPresent() }
+ } finally {
+ executorService.shutdownNow()
+ }
+ }
+}
diff --git a/sample-operators/kotlin-operator/README.md b/sample-operators/kotlin-operator/README.md
new file mode 100644
index 0000000000..c54f1e8cac
--- /dev/null
+++ b/sample-operators/kotlin-operator/README.md
@@ -0,0 +1,15 @@
+# Kotlin Operator Sample
+
+This module is a minimalist end-to-end test verifying that JOSDK works properly when both the
+custom resource and the reconciler are implemented in Kotlin rather than Java.
+
+The `ConfigMapCopyReconciler` reads `spec.message` from a `ConfigMapCopy` custom resource and
+copies it into a `ConfigMap`. This exercises:
+
+- deserialization of the custom resource from the Kubernetes API via the fabric8 client,
+- a Kotlin-implemented `Reconciler`, and
+- the rest of the reconciliation runtime (event sources, status updates, owner references).
+
+See [`KotlinCheckedExceptionDependentResourceTest`](../../operator-framework-core/src/test/kotlin/io/javaoperatorsdk/operator/processing/dependent/workflow/KotlinCheckedExceptionDependentResourceTest.kt)
+for a narrower unit-level test covering checked-exception handling specifically (see
+[#2967](https://github.com/operator-framework/java-operator-sdk/issues/2967)).
diff --git a/sample-operators/kotlin-operator/k8s/operator.yaml b/sample-operators/kotlin-operator/k8s/operator.yaml
new file mode 100644
index 0000000000..9e724a2860
--- /dev/null
+++ b/sample-operators/kotlin-operator/k8s/operator.yaml
@@ -0,0 +1,71 @@
+#
+# Copyright Java Operator SDK 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
+#
+# http://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.
+#
+
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: kotlin-operator
+
+---
+apiVersion: v1
+kind: Pod
+metadata:
+ name: kotlin-operator
+spec:
+ serviceAccountName: kotlin-operator
+ containers:
+ - name: operator
+ image: kotlin-sample-operator
+ imagePullPolicy: Never
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: kotlin-operator
+subjects:
+ - kind: ServiceAccount
+ name: kotlin-operator
+roleRef:
+ kind: ClusterRole
+ name: kotlin-operator
+ apiGroup: rbac.authorization.k8s.io
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: kotlin-operator
+rules:
+ - apiGroups:
+ - "apiextensions.k8s.io"
+ resources:
+ - customresourcedefinitions
+ verbs:
+ - '*'
+ - apiGroups:
+ - "sample.javaoperatorsdk"
+ resources:
+ - configmapcopies
+ - configmapcopies/status
+ verbs:
+ - '*'
+ - apiGroups:
+ - ""
+ resources:
+ - configmaps
+ verbs:
+ - '*'
diff --git a/sample-operators/kotlin-operator/pom.xml b/sample-operators/kotlin-operator/pom.xml
new file mode 100644
index 0000000000..532aecbf1b
--- /dev/null
+++ b/sample-operators/kotlin-operator/pom.xml
@@ -0,0 +1,175 @@
+
+
+
+ 4.0.0
+
+
+ io.javaoperatorsdk
+ sample-operators
+ 5.5.1-SNAPSHOT
+
+
+ sample-kotlin-operator
+ jar
+ Operator SDK - Samples - Kotlin Operator
+ Minimalist operator implemented in Kotlin, copying a value from a custom
+ resource's spec into a ConfigMap, verifying JOSDK works properly end-to-end when used from
+ Kotlin
+
+
+ 2.4.10
+
+
+
+
+
+ io.javaoperatorsdk
+ operator-framework-bom
+ ${project.version}
+ pom
+ import
+
+
+
+
+
+
+ io.javaoperatorsdk
+ operator-framework
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${kotlin.version}
+
+
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+ compile
+
+
+ org.apache.logging.log4j
+ log4j-core
+ compile
+
+
+ org.awaitility
+ awaitility
+ test
+
+
+ io.javaoperatorsdk
+ operator-framework-junit
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+ ${java.version}
+
+
+
+ kotlin-compile
+
+ compile
+
+ process-sources
+
+
+ ${project.basedir}/src/main/kotlin
+
+
+
+
+ kotlin-test-compile
+
+ test-compile
+
+ process-test-sources
+
+
+ ${project.basedir}/src/test/kotlin
+
+
+
+
+
+
+
+ io.fabric8
+ crd-generator-maven-plugin
+ ${fabric8-client.version}
+
+
+
+ generate
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ 0
+
+
+
+ com.google.cloud.tools
+ jib-maven-plugin
+ ${jib-maven-plugin.version}
+
+
+ gcr.io/distroless/java17-debian11
+
+
+ kotlin-sample-operator
+
+
+ io.javaoperatorsdk.operator.sample.ConfigMapCopyOperatorKt
+
+
+
+
+
+
+
diff --git a/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopy.kt b/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopy.kt
new file mode 100644
index 0000000000..dcf7605d6d
--- /dev/null
+++ b/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopy.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright Java Operator SDK 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
+ *
+ * http://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.javaoperatorsdk.operator.sample
+
+import io.fabric8.kubernetes.api.model.Namespaced
+import io.fabric8.kubernetes.client.CustomResource
+import io.fabric8.kubernetes.model.annotation.Group
+import io.fabric8.kubernetes.model.annotation.Version
+
+@Group("sample.javaoperatorsdk")
+@Version("v1")
+class ConfigMapCopy : CustomResource(), Namespaced
diff --git a/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyOperator.kt b/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyOperator.kt
new file mode 100644
index 0000000000..4b764e580c
--- /dev/null
+++ b/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyOperator.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright Java Operator SDK 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
+ *
+ * http://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.javaoperatorsdk.operator.sample
+
+import io.javaoperatorsdk.operator.Operator
+
+fun main() {
+ val operator = Operator()
+ operator.register(ConfigMapCopyReconciler())
+ operator.start()
+}
diff --git a/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyReconciler.kt b/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyReconciler.kt
new file mode 100644
index 0000000000..8255d125d1
--- /dev/null
+++ b/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyReconciler.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright Java Operator SDK 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
+ *
+ * http://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.javaoperatorsdk.operator.sample
+
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder
+import io.fabric8.kubernetes.api.model.ObjectMetaBuilder
+import io.javaoperatorsdk.operator.api.reconciler.Context
+import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration
+import io.javaoperatorsdk.operator.api.reconciler.Reconciler
+import io.javaoperatorsdk.operator.api.reconciler.UpdateControl
+
+/**
+ * Minimalist reconciler verifying that JOSDK works properly end-to-end when both the custom
+ * resource and the reconciler are implemented in Kotlin: it copies `spec.message` into a
+ * `ConfigMap`, exercising deserialization of the custom resource by the fabric8 client as well as
+ * the rest of the reconciliation runtime.
+ */
+@ControllerConfiguration
+class ConfigMapCopyReconciler : Reconciler {
+
+ companion object {
+ const val MESSAGE_KEY = "message"
+
+ fun configMapName(resource: ConfigMapCopy): String = resource.metadata.name + "-config"
+ }
+
+ override fun reconcile(
+ resource: ConfigMapCopy,
+ context: Context
+ ): UpdateControl {
+ val data: Map = mapOf(MESSAGE_KEY to resource.spec.message)
+ val configMap =
+ ConfigMapBuilder()
+ .withMetadata(
+ ObjectMetaBuilder()
+ .withName(configMapName(resource))
+ .withNamespace(resource.metadata.namespace)
+ .build())
+ .withData(data)
+ .build()
+ configMap.addOwnerReference(resource)
+
+ context.client.resource(configMap).serverSideApply()
+
+ resource.status = ConfigMapCopyStatus()
+ resource.status.configMapName = configMap.metadata.name
+ resource.status.observedMessage = resource.spec.message
+
+ return UpdateControl.patchStatus(resource)
+ }
+}
diff --git a/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopySpec.kt b/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopySpec.kt
new file mode 100644
index 0000000000..84bd10acdf
--- /dev/null
+++ b/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopySpec.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright Java Operator SDK 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
+ *
+ * http://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.javaoperatorsdk.operator.sample
+
+class ConfigMapCopySpec {
+ var message: String = ""
+}
diff --git a/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyStatus.kt b/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyStatus.kt
new file mode 100644
index 0000000000..49f9fe87a0
--- /dev/null
+++ b/sample-operators/kotlin-operator/src/main/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyStatus.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright Java Operator SDK 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
+ *
+ * http://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.javaoperatorsdk.operator.sample
+
+class ConfigMapCopyStatus {
+ var configMapName: String? = null
+ var observedMessage: String? = null
+}
diff --git a/sample-operators/kotlin-operator/src/main/resources/log4j2.xml b/sample-operators/kotlin-operator/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000..147f494c1d
--- /dev/null
+++ b/sample-operators/kotlin-operator/src/main/resources/log4j2.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-operators/kotlin-operator/src/test/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyE2E.kt b/sample-operators/kotlin-operator/src/test/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyE2E.kt
new file mode 100644
index 0000000000..e0676528b3
--- /dev/null
+++ b/sample-operators/kotlin-operator/src/test/kotlin/io/javaoperatorsdk/operator/sample/ConfigMapCopyE2E.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright Java Operator SDK 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
+ *
+ * http://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.javaoperatorsdk.operator.sample
+
+import io.fabric8.kubernetes.api.model.ConfigMap
+import io.fabric8.kubernetes.api.model.ObjectMetaBuilder
+import io.fabric8.kubernetes.client.KubernetesClientBuilder
+import io.javaoperatorsdk.operator.junit.AbstractOperatorExtension
+import io.javaoperatorsdk.operator.junit.ClusterDeployedOperatorExtension
+import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension
+import java.io.FileInputStream
+import java.time.Duration
+import org.assertj.core.api.Assertions.assertThat
+import org.awaitility.Awaitility.await
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * Dual-mode (local and remote) E2E test exercising [ConfigMapCopyReconciler]: creates a
+ * [ConfigMapCopy] resource, asserts a [ConfigMap] is created with the message copied from spec,
+ * updates the message, and verifies the [ConfigMap] is garbage collected on deletion via the
+ * owner reference.
+ */
+class ConfigMapCopyE2E {
+
+ companion object {
+ private val log: Logger = LoggerFactory.getLogger(ConfigMapCopyE2E::class.java)
+
+ const val TEST_RESOURCE_NAME = "test-config-map-copy"
+ const val INITIAL_MESSAGE = "hello from kotlin"
+ const val UPDATED_MESSAGE = "updated from kotlin"
+ const val GARBAGE_COLLECTION_TIMEOUT_SECONDS = 90L
+
+ private val client = KubernetesClientBuilder().build()
+
+ fun isLocal(): Boolean {
+ val deployment = System.getProperty("test.deployment")
+ val remote = deployment != null && deployment == "remote"
+ log.info("Running the operator " + (if (remote) "remotely" else "locally"))
+ return !remote
+ }
+ }
+
+ @RegisterExtension
+ val operator: AbstractOperatorExtension =
+ if (isLocal())
+ LocallyRunOperatorExtension.builder().withReconciler(ConfigMapCopyReconciler()).build()
+ else
+ ClusterDeployedOperatorExtension.builder()
+ .withOperatorDeployment(
+ FileInputStream("k8s/operator.yaml").use { client.load(it).items() })
+ .build()
+
+ @Test
+ fun copiesMessageFromSpecIntoConfigMap() {
+ val resource = operator.create(testResource(INITIAL_MESSAGE))
+
+ await().untilAsserted {
+ val updated = operator.get(ConfigMapCopy::class.java, TEST_RESOURCE_NAME)
+ assertThat(updated.status).isNotNull()
+ assertThat(updated.status.observedMessage).isEqualTo(INITIAL_MESSAGE)
+
+ val configMap =
+ operator.get(ConfigMap::class.java, ConfigMapCopyReconciler.configMapName(resource))
+ assertThat(configMap).isNotNull()
+ assertThat(configMap.data[ConfigMapCopyReconciler.MESSAGE_KEY]).isEqualTo(INITIAL_MESSAGE)
+ }
+
+ val toUpdate = operator.get(ConfigMapCopy::class.java, TEST_RESOURCE_NAME)
+ toUpdate.spec.message = UPDATED_MESSAGE
+ operator.replace(toUpdate)
+
+ await().untilAsserted {
+ val configMap =
+ operator.get(ConfigMap::class.java, ConfigMapCopyReconciler.configMapName(resource))
+ assertThat(configMap.data[ConfigMapCopyReconciler.MESSAGE_KEY]).isEqualTo(UPDATED_MESSAGE)
+ }
+
+ operator.delete(toUpdate)
+
+ await()
+ .atMost(Duration.ofSeconds(GARBAGE_COLLECTION_TIMEOUT_SECONDS))
+ .untilAsserted {
+ assertThat(
+ operator.get(ConfigMap::class.java, ConfigMapCopyReconciler.configMapName(resource)))
+ .isNull()
+ }
+ }
+
+ private fun testResource(message: String): ConfigMapCopy {
+ val resource = ConfigMapCopy()
+ resource.metadata =
+ ObjectMetaBuilder().withName(TEST_RESOURCE_NAME).withNamespace(operator.namespace).build()
+ resource.spec = ConfigMapCopySpec().apply { this.message = message }
+ return resource
+ }
+}
diff --git a/sample-operators/kotlin-operator/src/test/resources/log4j2.xml b/sample-operators/kotlin-operator/src/test/resources/log4j2.xml
new file mode 100644
index 0000000000..8b1c5ca270
--- /dev/null
+++ b/sample-operators/kotlin-operator/src/test/resources/log4j2.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml
index 765feea520..1d35c908f2 100644
--- a/sample-operators/pom.xml
+++ b/sample-operators/pom.xml
@@ -36,5 +36,6 @@
leader-election
controller-namespace-deletion
operations
+ kotlin-operator